OpenCores
URL https://opencores.org/ocsvn/openrisc_me/openrisc_me/trunk

Subversion Repositories openrisc_me

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /openrisc/trunk/rtos/ecos-2.0/packages/io/usb/slave/v2_0/tests
    from Rev 27 to Rev 174
    Reverse comparison

Rev 27 → Rev 174

/usbtarget.c
0,0 → 1,1848
/*{{{ Banner */
 
/*=================================================================
//
// target.c
//
// USB testing - target-side
//
//==========================================================================
//####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####
//
// This program performs appropriate USB initialization and initializes
// itself as a specific type of USB peripheral, Red Hat eCos testing.
// There is no actual host-side device driver for this, instead there is
// a test application which performs ioctl's on /proc/bus/usb/... and
// makes appropriate functionality available to a Tcl script.
//
// Author(s): bartv
// Date: 2001-07-04
//####DESCRIPTIONEND####
//==========================================================================
*/
 
/*}}}*/
/*{{{ #include's */
 
#include <stdio.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/diag.h>
#include <cyg/kernel/kapi.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/io/io.h>
#include <cyg/io/usb/usbs.h>
#include <cyg/infra/testcase.h>
#include "protocol.h"
 
/*}}}*/
 
/*{{{ Statics */
 
// ----------------------------------------------------------------------------
// Statics.
 
// The number of endpoints supported by the device driver.
static int number_endpoints = 0;
 
// The control endpoint
static usbs_control_endpoint* control_endpoint = (usbs_control_endpoint*) 0;
 
// Buffers for incoming and outgoing data, and a length field.
static unsigned char class_request[USBTEST_MAX_CONTROL_DATA + 1];
static unsigned char class_reply[USBTEST_MAX_CONTROL_DATA + 1];
static int class_request_size = 0;
 
// This semaphore is used by DSRs to wake up the main thread when work has to
// be done at thread level.
static cyg_sem_t main_wakeup;
 
// And this function pointer identifies the work that has to be done.
static void (*main_thread_action)(void) = (void (*)(void)) 0;
 
// Is the system still busy processing a previous request? This variable is
// checked in response to the synch request. It may get updated in
// DSRs as well as at thread level, hence volatile.
static volatile int idle = 1;
 
// Are any tests currently running?
static int running = 0;
 
// Has the current batch of tests been terminated by the host? This
// flag is checked by the various test handlers at appropriate
// intervals, and helps to handle the case where one of the side has
// terminated early because an error has been detected.
static int current_tests_terminated = 0;
 
// A counter for the number of threads involved in the current batch of tests.
static int thread_counter = 0;
 
// An extra buffer for recovery operations, for example to accept and discard
// data which the host is still trying to send.
static unsigned char recovery_buffer[USBTEST_MAX_BULK_DATA + USBTEST_MAX_BULK_DATA_EXTRA];
 
/*}}}*/
/*{{{ Logging */
 
// ----------------------------------------------------------------------------
// The target-side code can provide various levels of run-time logging.
// Obviously the verbose flag cannot be controlled by a command-line
// argument, but it can be set from inside gdb or alternatively by
// a request from the host.
//
// NOTE: is printf() the best I/O routine to use here?
 
static int verbose = 0;
 
#define VERBOSE(_level_, _format_, _args_...) \
do { \
if (verbose >= _level_) { \
diag_printf(_format_, ## _args_); \
} \
} while (0);
 
/*}}}*/
/*{{{ Utilities */
 
// ----------------------------------------------------------------------------
// A reimplementation of nanosleep, to avoid having to pull in the
// POSIX compatibility testing for USB testing.
static void
usbs_nanosleep(int delay)
{
cyg_tick_count_t ticks;
cyg_resolution_t resolution = cyg_clock_get_resolution(cyg_real_time_clock());
 
// (resolution.dividend/resolution.divisor) == nanoseconds/tick
// e.g. 1000000000/100, i.e. 10000000 ns or 10 ms per tick
// So ticks = (delay * divisor) / dividend
// e.g. (10000000 * 100) / 1000000000
// with a likely value of 0 for delays of less than the clock resolution,
// so round those up to one tick.
 
cyg_uint64 tmp = (cyg_uint64) delay;
tmp *= (cyg_uint64) resolution.divisor;
tmp /= (cyg_uint64) resolution.dividend;
 
ticks = (int) tmp;
if (0 != ticks) {
cyg_thread_delay(ticks);
}
}
 
// ----------------------------------------------------------------------------
// Fix any problems in the driver-supplied endpoint data
//
// Maximum transfer sizes are limited not just by the capabilities
// of the driver but also by the testing code itself, since e.g.
// buffers for transfers are statically allocated.
static void
fix_driver_endpoint_data(void)
{
int i;
for (i = 0; !USBS_TESTING_ENDPOINTS_IS_TERMINATOR(usbs_testing_endpoints[i]); i++) {
if (USB_ENDPOINT_DESCRIPTOR_ATTR_BULK == usbs_testing_endpoints[i].endpoint_type) {
if ((-1 == usbs_testing_endpoints[i].max_size) ||
(usbs_testing_endpoints[i].max_size > USBTEST_MAX_BULK_DATA)) {
usbs_testing_endpoints[i].max_size = USBTEST_MAX_BULK_DATA;
}
}
}
}
 
// ----------------------------------------------------------------------------
// A heartbeat thread.
//
// USB tests can run for a long time with no traffic on the debug channel,
// which can cause problems. To avoid problems it is possible to have a
// heartbeat thread running in the background, sending output at one
// second intervals.
//
// Depending on the configuration the output may still be line-buffered,
// but that is still sufficient to keep things happy.
 
static cyg_bool heartbeat = false;
static cyg_thread heartbeat_data;
static cyg_handle_t heartbeat_handle;
static char heartbeat_stack[CYGNUM_HAL_STACK_SIZE_TYPICAL];
 
static void
heartbeat_function(cyg_addrword_t arg __attribute((unused)))
{
char* message = "alive\n";
int i;
for ( i = 0; ; i = (i + 1) % 6) {
usbs_nanosleep(1000000000);
if (heartbeat) {
diag_write_char(message[i]);
}
}
}
 
static void
start_heartbeat(void)
{
cyg_thread_create( 0, &heartbeat_function, 0,
"heartbeat", heartbeat_stack, CYGNUM_HAL_STACK_SIZE_TYPICAL,
&heartbeat_handle, &heartbeat_data);
cyg_thread_resume(heartbeat_handle);
}
 
 
/*}}}*/
/*{{{ Endpoint usage */
 
// ----------------------------------------------------------------------------
// It is important to keep track of which endpoints are currently in use,
// because the behaviour of the USB I/O routines is undefined if there are
// concurrent attempts to communicate on the same endpoint. Normally this is
// not a problem because the host will ensure that a given endpoint is used
// for only one endpoint at a time, but when performing recovery action it
// is important that the system is sure that a given endpoint can be accessed
// safely.
 
static cyg_bool in_endpoint_in_use[16];
static cyg_bool out_endpoint_in_use[16];
 
// Lock the given endpoint. In theory this is only ever accessed from a single
// test thread at a time, but just in case...
static void
lock_endpoint(int endpoint, int direction)
{
CYG_ASSERTC((endpoint >=0) && (endpoint < 16));
CYG_ASSERTC((USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) || (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT == direction));
cyg_scheduler_lock();
if (0 == endpoint) {
// Comms traffic on endpoint 0 is implemented using reserved control messages.
// It is not really possible to have concurrent IN and OUT operations because
// the two would interfere with each other.
CYG_ASSERTC(!in_endpoint_in_use[0] && !out_endpoint_in_use[0]);
in_endpoint_in_use[0] = true;
out_endpoint_in_use[0] = true;
} else if (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) {
CYG_ASSERTC(!in_endpoint_in_use[endpoint]);
in_endpoint_in_use[endpoint] = true;
} else {
CYG_ASSERTC(!out_endpoint_in_use[endpoint]);
out_endpoint_in_use[endpoint] = true;
}
cyg_scheduler_unlock();
}
 
static void
unlock_endpoint(int endpoint, int direction)
{
CYG_ASSERTC((endpoint >= 0) && (endpoint < 16));
CYG_ASSERTC((USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) || (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT == direction));
if (0 == endpoint) {
CYG_ASSERTC(in_endpoint_in_use[0] && out_endpoint_in_use[0]);
in_endpoint_in_use[0] = false;
out_endpoint_in_use[0] = false;
} else if (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) {
CYG_ASSERTC(in_endpoint_in_use[endpoint]);
in_endpoint_in_use[endpoint] = false;
} else {
CYG_ASSERTC(out_endpoint_in_use[endpoint]);
out_endpoint_in_use[endpoint] = false;
}
}
 
static cyg_bool
is_endpoint_locked(int endpoint, int direction)
{
cyg_bool result = false;
if (0 == endpoint) {
result = in_endpoint_in_use[0];
} else if (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) {
result = in_endpoint_in_use[endpoint];
} else {
result = out_endpoint_in_use[endpoint];
}
return result;
}
 
// For a given endpoint number, direction and protocol, search through the table
// supplied by the device driver of all available endpoints. This can be used
// to e.g. get hold of the name of the devtab entry or a pointer to the endpoint
// data structure itself.
static int
lookup_endpoint(int endpoint_number, int direction, int protocol)
{
int result = -1;
int i;
 
for (i = 0; !USBS_TESTING_ENDPOINTS_IS_TERMINATOR(usbs_testing_endpoints[i]); i++) {
if ((usbs_testing_endpoints[i].endpoint_type == protocol) &&
(usbs_testing_endpoints[i].endpoint_number == endpoint_number) &&
(usbs_testing_endpoints[i].endpoint_direction == direction)) {
result = i;
break;
}
}
return result;
}
 
/*}}}*/
/*{{{ Enumeration data */
 
// ----------------------------------------------------------------------------
// The enumeration data.
//
// For simplicity this configuration involves just a single interface.
// The target has to list all the endpoints, or the Linux kernel will
// not allow application code to access them. Hence the information
// provided by the device drivers has to be turned into endpoint descriptors.
 
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, 0),
total_length_hi: USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_HI(1, 0),
number_interfaces: 1,
configuration_id: 1, // id 0 is special according to the spec
configuration_str: 0,
attributes: USB_CONFIGURATION_DESCRIPTOR_ATTR_REQUIRED |
USB_CONFIGURATION_DESCRIPTOR_ATTR_SELF_POWERED,
max_power: 50
};
 
usb_interface_descriptor usb_interface = {
length: USB_INTERFACE_DESCRIPTOR_LENGTH,
type: USB_INTERFACE_DESCRIPTOR_TYPE,
interface_id: 0,
alternate_setting: 0,
number_endpoints: 0,
interface_class: USB_INTERFACE_DESCRIPTOR_CLASS_VENDOR,
interface_subclass: USB_INTERFACE_DESCRIPTOR_SUBCLASS_VENDOR,
interface_protocol: USB_INTERFACE_DESCRIPTOR_PROTOCOL_VENDOR,
interface_str: 0
};
 
usb_endpoint_descriptor usb_endpoints[USBTEST_MAX_ENDPOINTS];
const unsigned char* usb_strings[] = {
"\004\003\011\004",
"\020\003R\000e\000d\000 \000H\000a\000t\000",
"\054\003R\000e\000d\000 \000H\000a\000t\000 \000e\000C\000o\000s\000 \000"
"U\000S\000B\000 \000t\000e\000s\000t\000"
};
 
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, // Note: this is not an allocated vendor id
vendor_hi: 0x42,
product_lo: 0x00,
product_hi: 0x01,
device_lo: 0x00,
device_hi: 0x01,
manufacturer_str: 1,
product_str: 2,
serial_number_str: 0,
number_configurations: 1
},
total_number_interfaces: 1,
total_number_endpoints: 0,
total_number_strings: 3,
configurations: &usb_configuration,
interfaces: &usb_interface,
endpoints: usb_endpoints,
strings: usb_strings
};
 
static void
provide_endpoint_enumeration_data(void)
{
int enum_endpoint_count = 0;
int i;
 
for (i = 0; !USBS_TESTING_ENDPOINTS_IS_TERMINATOR(usbs_testing_endpoints[i]); i++) {
 
// The control endpoint need not appear in the enumeration data.
if (USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL == usbs_testing_endpoints[i].endpoint_type) {
continue;
}
 
usb_endpoints[enum_endpoint_count].length = USB_ENDPOINT_DESCRIPTOR_LENGTH;
usb_endpoints[enum_endpoint_count].type = USB_ENDPOINT_DESCRIPTOR_TYPE;
usb_endpoints[enum_endpoint_count].endpoint = usbs_testing_endpoints[i].endpoint_number |
usbs_testing_endpoints[i].endpoint_direction;
 
switch (usbs_testing_endpoints[i].endpoint_type) {
case USB_ENDPOINT_DESCRIPTOR_ATTR_BULK:
usb_endpoints[enum_endpoint_count].attributes = USB_ENDPOINT_DESCRIPTOR_ATTR_BULK;
usb_endpoints[enum_endpoint_count].max_packet_lo = 64;
usb_endpoints[enum_endpoint_count].max_packet_hi = 0;
usb_endpoints[enum_endpoint_count].interval = 0;
break;
case USB_ENDPOINT_DESCRIPTOR_ATTR_ISOCHRONOUS:
usb_endpoints[enum_endpoint_count].attributes = USB_ENDPOINT_DESCRIPTOR_ATTR_ISOCHRONOUS;
usb_endpoints[enum_endpoint_count].max_packet_lo = usbs_testing_endpoints[i].max_size & 0x0FF;
usb_endpoints[enum_endpoint_count].max_packet_hi = (usbs_testing_endpoints[i].max_size >> 8) & 0x0FF;
usb_endpoints[enum_endpoint_count].interval = 1;
break;
case USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT:
usb_endpoints[enum_endpoint_count].attributes = USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT;
usb_endpoints[enum_endpoint_count].max_packet_lo = (unsigned char) usbs_testing_endpoints[i].max_size;
usb_endpoints[enum_endpoint_count].max_packet_hi = 0;
usb_endpoints[enum_endpoint_count].interval = 1; // NOTE: possibly incorrect
break;
}
 
enum_endpoint_count++;
}
 
usb_interface.number_endpoints = enum_endpoint_count;
usb_enum_data.total_number_endpoints = enum_endpoint_count;
usb_configuration.total_length_lo = USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_LO(1, enum_endpoint_count);
usb_configuration.total_length_hi = USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_HI(1, enum_endpoint_count);
}
 
/*}}}*/
/*{{{ Host/target common code */
 
#define TARGET
#include "common.c"
 
/*}}}*/
/*{{{ The tests */
 
/*{{{ UsbTest structure */
 
// ----------------------------------------------------------------------------
// All the information associated with a particular testcase. Much of this
// is identical to the equivalent host-side structure, but some additional
// information is needed so the structure and associated routines are not
// shared.
typedef struct UsbTest {
 
// A unique identifier to make verbose output easier to understand
int id;
// Which test should be run
usbtest which_test;
 
// Test-specific details.
union {
UsbTest_Bulk bulk;
UsbTest_ControlIn control_in;
} test_params;
 
// How to recover from any problems. Specifically, what kind of message
// could the target send or receive that would unlock the thread on this
// side.
UsbTest_Recovery recovery;
 
// The test result, to be collected and passed back to the host.
int result_pass;
char result_message[USBTEST_MAX_MESSAGE];
 
// Support for synchronization. This allows the UsbTest structure to be
// used as the callback data for low-level USB calls.
cyg_sem_t sem;
int transferred;
 
// Some tests may need extra cancellation support
void (*cancel_fn)(struct UsbTest*);
unsigned char buffer[USBTEST_MAX_BULK_DATA + USBTEST_MAX_BULK_DATA_EXTRA];
} UsbTest;
 
// Reset the information in a given test. This is used by the pool allocation
// code. The data union is left alone, filling in the appropriate union
// member is left to other code.
static void
reset_usbtest(UsbTest* test)
{
static int next_id = 1;
test->id = next_id++;
test->which_test = usbtest_invalid;
usbtest_recovery_reset(&(test->recovery));
test->result_pass = 0;
test->result_message[0] = '\0';
cyg_semaphore_init(&(test->sem), 0);
test->transferred = 0;
test->cancel_fn = (void (*)(UsbTest*)) 0;
}
 
// Forward declaration. The pool code depends on run_test(), setting up a test requires the pool.
static UsbTest* pool_allocate(void);
 
/*}}}*/
/*{{{ Bulk transfers */
 
/*{{{ handle_test_bulk() */
 
// Prepare for a bulk transfer test. This means allocating a thread to do
// the work, and extracting the test parameters from the current buffer.
// The thread allocation code does not require any locking since all worker
// threads should be idle when starting a new thread, so the work can be
// done entirely at DSR level and no synch is required.
static usbs_control_return
handle_test_bulk(usb_devreq* req)
{
UsbTest* test;
int index = 0;
 
test = pool_allocate();
unpack_usbtest_bulk(&(test->test_params.bulk), class_request, &index);
test->which_test = (USB_DEVREQ_DIRECTION_IN == (test->test_params.bulk.endpoint & USB_DEVREQ_DIRECTION_MASK)) ?
usbtest_bulk_in : usbtest_bulk_out;
 
VERBOSE(3, "Preparing USB bulk test on endpoint %d, direction %s, for %d packets\n", \
test->test_params.bulk.endpoint & ~USB_DEVREQ_DIRECTION_MASK, \
(usbtest_bulk_in == test->which_test) ? "IN" : "OUT", \
test->test_params.bulk.number_packets);
VERBOSE(3, " I/O mechanism is %s\n", \
(usb_io_mechanism_usb == test->test_params.bulk.io_mechanism) ? "low-level USB" : \
(usb_io_mechanism_dev == test->test_params.bulk.io_mechanism) ? "devtab" : "<invalid>");
VERBOSE(3, " Data format %s, data1 %d, data* %d, data+ %d, data1* %d, data1+ %d, data** %d, data*+ %d, data+* %d, data++ %d\n",\
(usbtestdata_none == test->test_params.bulk.data.format) ? "none" : \
(usbtestdata_bytefill == test->test_params.bulk.data.format) ? "bytefill" : \
(usbtestdata_wordfill == test->test_params.bulk.data.format) ? "wordfill" : \
(usbtestdata_byteseq == test->test_params.bulk.data.format) ? "byteseq" : \
(usbtestdata_wordseq == test->test_params.bulk.data.format) ? "wordseq" : "<invalid>", \
test->test_params.bulk.data.seed, \
test->test_params.bulk.data.multiplier, \
test->test_params.bulk.data.increment, \
test->test_params.bulk.data.transfer_seed_multiplier, \
test->test_params.bulk.data.transfer_seed_increment, \
test->test_params.bulk.data.transfer_multiplier_multiplier, \
test->test_params.bulk.data.transfer_multiplier_increment, \
test->test_params.bulk.data.transfer_increment_multiplier, \
test->test_params.bulk.data.transfer_increment_increment);
VERBOSE(3, " txsize1 %d, txsize>= %d, txsize<= %d, txsize* %d, txsize/ %d, txsize+ %d\n", \
test->test_params.bulk.tx_size, test->test_params.bulk.tx_size_min, \
test->test_params.bulk.tx_size_max, test->test_params.bulk.tx_size_multiplier, \
test->test_params.bulk.tx_size_divisor, test->test_params.bulk.tx_size_increment);
VERBOSE(3, " rxsize1 %d, rxsize>= %d, rxsize<= %d, rxsize* %d, rxsize/ %d, rxsize+ %d\n", \
test->test_params.bulk.rx_size, test->test_params.bulk.rx_size_min, \
test->test_params.bulk.rx_size_max, test->test_params.bulk.rx_size_multiplier, \
test->test_params.bulk.rx_size_divisor, test->test_params.bulk.rx_size_increment);
VERBOSE(3, " txdelay1 %d, txdelay>= %d, txdelay<= %d, txdelay* %d, txdelay/ %d, txdelay+ %d\n", \
test->test_params.bulk.tx_delay, test->test_params.bulk.tx_delay_min, \
test->test_params.bulk.tx_delay_max, test->test_params.bulk.tx_delay_multiplier, \
test->test_params.bulk.tx_delay_divisor, test->test_params.bulk.tx_delay_increment);
VERBOSE(3, " rxdelay1 %d, rxdelay>= %d, rxdelay<= %d, rxdelay* %d, rxdelay/ %d, rxdelay+ %d\n", \
test->test_params.bulk.rx_delay, test->test_params.bulk.rx_delay_min, \
test->test_params.bulk.rx_delay_max, test->test_params.bulk.rx_delay_multiplier, \
test->test_params.bulk.rx_delay_divisor, test->test_params.bulk.rx_delay_increment);
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ run_test_bulk_out() */
 
// The same callback can be used for IN and OUT transfers. Note that
// starting the next transfer is left to the thread, it is not done
// at DSR level.
static void
run_test_bulk_in_out_callback(void* callback_arg, int transferred)
{
UsbTest* test = (UsbTest*) callback_arg;
test->transferred = transferred;
cyg_semaphore_post(&(test->sem));
}
 
// OUT transfers, i.e. the host will be sending some number of
// packets. The I/O can happen in a number of different ways, e.g. via
// the low-level USB API or via devtab routines.
static void
run_test_bulk_out(UsbTest* test)
{
unsigned char* buf;
int endpoint_number = test->test_params.bulk.endpoint & ~USB_DEVREQ_DIRECTION_MASK;
int ep_index;
usbs_rx_endpoint* endpoint = 0;
cyg_io_handle_t io_handle = (cyg_io_handle_t)0;
int alignment;
int transferred;
int i;
 
VERBOSE(1, "Starting test %d, bulk out on endpoint %d\n", test->id, endpoint_number);
 
ep_index = lookup_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, USB_ENDPOINT_DESCRIPTOR_ATTR_BULK);
if (ep_index == -1) {
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk OUT transfer on endpoint %d: no such bulk endpoint", endpoint_number);
return;
}
endpoint = (usbs_rx_endpoint*) usbs_testing_endpoints[ep_index].endpoint;
alignment = usbs_testing_endpoints[ep_index].alignment;
if (0 != alignment) {
buf = (unsigned char*) ((((cyg_uint32)test->buffer) + alignment - 1) & ~(alignment - 1));
} else {
buf = test->buffer;
}
CYG_ASSERTC((usb_io_mechanism_usb == test->test_params.bulk.io_mechanism) || \
(usb_io_mechanism_dev == test->test_params.bulk.io_mechanism));
if (usb_io_mechanism_dev == test->test_params.bulk.io_mechanism) {
if (((const char*)0 == usbs_testing_endpoints[ep_index].devtab_entry) ||
(0 != cyg_io_lookup(usbs_testing_endpoints[ep_index].devtab_entry, &io_handle))) {
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk OUT transfer on endpoint %d: no devtab entry", endpoint_number);
return;
}
}
 
// Make sure nobody else is using this endpoint
lock_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT);
 
for (i = 0; i < test->test_params.bulk.number_packets; i++) {
int rx_size = test->test_params.bulk.rx_size;
int tx_size = test->test_params.bulk.tx_size;
 
VERBOSE(2, "Bulk OUT test %d: iteration %d, rx size %d, tx size %d\n", test->id, i, rx_size, tx_size);
if (rx_size < tx_size) {
rx_size = tx_size;
VERBOSE(2, "Bulk OUT test %d: iteration %d, packet size reset to %d to match tx size\n",
test->id, i, rx_size);
}
test->recovery.endpoint = endpoint_number | USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT;
test->recovery.protocol = USB_ENDPOINT_DESCRIPTOR_ATTR_BULK;
test->recovery.size = rx_size;
 
// Make sure there is no old data lying around
if (usbtestdata_none != test->test_params.bulk.data.format) {
memset(buf, 0, rx_size);
}
 
// Do the actual transfer, using the I/O mechanism specified for this test.
switch (test->test_params.bulk.io_mechanism)
{
case usb_io_mechanism_usb :
{
test->transferred = 0;
usbs_start_rx_buffer(endpoint, buf, rx_size, &run_test_bulk_in_out_callback, (void*) test);
cyg_semaphore_wait(&(test->sem));
transferred = test->transferred;
break;
}
 
case usb_io_mechanism_dev :
{
int result;
transferred = rx_size;
result = cyg_io_read(io_handle, (void*) buf, &transferred);
if (result < 0) {
transferred = result;
}
break;
}
 
default:
CYG_FAIL("Invalid test mechanism specified");
break;
}
 
// Has this test been aborted for some reason?
if (current_tests_terminated) {
VERBOSE(2, "Bulk OUT test %d: iteration %d, termination detected\n", test->id, i);
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk OUT transfer on endpoint %d: transfer aborted after iteration %d", endpoint_number, i);
break;
}
 
// If an error occurred, abort this run
if (transferred < 0) {
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk OUT transfer on endpoint %d: transfer failed with %d", endpoint_number, transferred);
VERBOSE(2, "Bulk OUT test %d: iteration %d, error:\n %s\n", test->id, i, test->result_message);
break;
}
 
// Did the host send the expected amount of data?
if (transferred < test->test_params.bulk.tx_size) {
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk OUT transfer on endpoint %d : the host only sent %d bytes when %d were expected",
endpoint_number, transferred, tx_size);
VERBOSE(2, "Bulk OUT test %d: iteration %d, error:\n %s\n", test->id, i, test->result_message);
break;
}
 
if (verbose >= 3) {
// Output the first 32 bytes of data
char msg[256];
int index;
int j;
index = snprintf(msg, 255, "Bulk OUT test %d: iteration %d, transferred %d\n Data %s:",
test->id, i, transferred,
(usbtestdata_none == test->test_params.bulk.data.format) ? "(uninitialized)" : "");
 
for (j = 0; ((j + 3) < transferred) && (j < 32); j+= 4) {
index += snprintf(msg+index, 255-index, " %02x%02x%02x%02x",
buf[j], buf[j+1], buf[j+2], buf[j+3]);
}
if (j < 32) {
index += snprintf(msg+index, 255-index, " ");
for ( ; j < transferred; j++) {
index += snprintf(msg+index, 255-index, "%02x", buf[j]);
}
}
VERBOSE(3, "%s\n", msg);
}
// Is the data correct?
if (!usbtest_check_buffer(&(test->test_params.bulk.data), buf, transferred)) {
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk OUT transfer on endpoint %d : mismatch between received and expected data", endpoint_number);
VERBOSE(2, "Bulk OUt test %d: iteration %d, error:\n %s\n", test->id, i, test->result_message);
break;
}
 
if (0 != test->test_params.bulk.rx_delay) {
VERBOSE(2, "Bulk OUT test %d: iteration %d, sleeping for %d nanoseconds\n", test->id, \
i, test->test_params.bulk.rx_delay);
usbs_nanosleep(test->test_params.bulk.rx_delay);
}
// Move on to the next transfer
USBTEST_BULK_NEXT(test->test_params.bulk);
}
 
// Always unlock the endpoint on completion
unlock_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT);
 
// If all the packets have been transferred this test has passed.
if (i >= test->test_params.bulk.number_packets) {
test->result_pass = 1;
}
VERBOSE(1, "Test %d bulk OUT on endpoint %d, result %d\n", test->id, endpoint_number, test->result_pass);
}
 
/*}}}*/
/*{{{ run_test_bulk_in() */
 
// IN transfers, i.e. the host is expected to receive some data. These are slightly
// easier than OUT transfers because it is the host that will do the checking.
static void
run_test_bulk_in(UsbTest* test)
{
unsigned char* buf;
int endpoint_number = test->test_params.bulk.endpoint & ~USB_DEVREQ_DIRECTION_MASK;
int ep_index;
usbs_tx_endpoint* endpoint = 0;
cyg_io_handle_t io_handle = (cyg_io_handle_t)0;
int alignment;
int transferred;
int i;
 
VERBOSE(1, "Starting test %d, bulk IN on endpoint %d\n", test->id, endpoint_number);
ep_index = lookup_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, USB_ENDPOINT_DESCRIPTOR_ATTR_BULK);
if (ep_index == -1) {
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk IN transfer on endpoint %d: no such bulk endpoint", endpoint_number);
return;
}
endpoint = (usbs_tx_endpoint*) usbs_testing_endpoints[ep_index].endpoint;
alignment = usbs_testing_endpoints[ep_index].alignment;
if (0 != alignment) {
buf = (unsigned char*) ((((cyg_uint32)test->buffer) + alignment - 1) & ~(alignment - 1));
} else {
buf = test->buffer;
}
CYG_ASSERTC((usb_io_mechanism_usb == test->test_params.bulk.io_mechanism) || \
(usb_io_mechanism_dev == test->test_params.bulk.io_mechanism));
if (usb_io_mechanism_dev == test->test_params.bulk.io_mechanism) {
if (((const char*)0 == usbs_testing_endpoints[ep_index].devtab_entry) ||
(0 != cyg_io_lookup(usbs_testing_endpoints[ep_index].devtab_entry, &io_handle))) {
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk IN transfer on endpoint %d: no devtab entry", endpoint_number);
return;
}
}
 
// Make sure nobody else is using this endpoint
lock_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
for (i = 0; i < test->test_params.bulk.number_packets; i++) {
int packet_size = test->test_params.bulk.tx_size;
test->recovery.endpoint = endpoint_number | USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN;
test->recovery.protocol = USB_ENDPOINT_DESCRIPTOR_ATTR_BULK;
test->recovery.size = packet_size + usbs_testing_endpoints[ep_index].max_in_padding;
 
// Make sure the buffer contains the data expected by the host
usbtest_fill_buffer(&(test->test_params.bulk.data), buf, packet_size);
if (verbose < 3) {
VERBOSE(2, "Bulk OUT test %d: iteration %d, packet size %d\n", test->id, i, packet_size);
} else {
// Output the first 32 bytes of data as well.
char msg[256];
int index;
int j;
index = snprintf(msg, 255, "Bulk IN test %d: iteration %d, packet size %d\n Data %s:",
test->id, i, packet_size,
(usbtestdata_none == test->test_params.bulk.data.format) ? "(uninitialized)" : "");
 
for (j = 0; ((j + 3) < packet_size) && (j < 32); j+= 4) {
index += snprintf(msg+index, 255-index, " %02x%02x%02x%02x",
buf[j], buf[j+1], buf[j+2], buf[j+3]);
}
if (j < 32) {
index += snprintf(msg+index, 255-index, " ");
for ( ; j < packet_size; j++) {
index += snprintf(msg+index, 255-index, "%02x", buf[j]);
}
}
VERBOSE(3, "%s\n", msg);
}
// Do the actual transfer, using the I/O mechanism specified for this test.
switch (test->test_params.bulk.io_mechanism)
{
case usb_io_mechanism_usb :
{
test->transferred = 0;
usbs_start_tx_buffer(endpoint, buf, packet_size, &run_test_bulk_in_out_callback, (void*) test);
cyg_semaphore_wait(&(test->sem));
transferred = test->transferred;
break;
}
 
case usb_io_mechanism_dev :
{
int result;
transferred = packet_size;
result = cyg_io_write(io_handle, (void*) buf, &transferred);
if (result < 0) {
transferred = result;
}
break;
}
 
default:
CYG_FAIL("Invalid test mechanism specified");
break;
}
 
// Has this test been aborted for some reason?
if (current_tests_terminated) {
VERBOSE(2, "Bulk IN test %d: iteration %d, termination detected\n", test->id, i);
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk IN transfer on endpoint %d : terminated on iteration %d, packet_size %d\n",
endpoint_number, i, packet_size);
break;
}
 
// If an error occurred, abort this run
if (transferred < 0) {
test->result_pass = 0;
snprintf(test->result_message, USBTEST_MAX_MESSAGE,
"Target, bulk IN transfer on endpoint %d: transfer failed with %d", endpoint_number, transferred);
VERBOSE(2, "Bulk IN test %d: iteration %d, error:\n %s\n", test->id, i, test->result_message);
break;
}
 
// No need to check the transfer size, the USB code is only
// allowed to send the exact amount of data requested.
 
if (0 != test->test_params.bulk.tx_delay) {
VERBOSE(2, "Bulk IN test %d: iteration %d, sleeping for %d nanoseconds\n", test->id, i, \
test->test_params.bulk.tx_delay);
usbs_nanosleep(test->test_params.bulk.tx_delay);
}
// Move on to the next transfer
USBTEST_BULK_NEXT(test->test_params.bulk);
}
 
// Always unlock the endpoint on completion
unlock_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
 
// If all the packets have been transferred this test has passed.
if (i >= test->test_params.bulk.number_packets) {
test->result_pass = 1;
}
VERBOSE(1, "Test %d bulk IN on endpoint %d, result %d\n", test->id, endpoint_number, test->result_pass);
}
 
/*}}}*/
 
/*}}}*/
/*{{{ Control IN transfers */
 
// Control-IN transfers. These have to be handled a little bit differently
// from bulk transfers. The target never actually initiates anything. Instead
// the host will send reserved control messages which are handled at DSR
// level and passed to handle_reserved_control_messages() below. Assuming
// a control-IN test is in progress, that will take appropriate action. The
// thread will be woken up only once all packets have been transferred, or
// on abnormal termination.
 
// Is a control-IN test currently in progress?
static UsbTest* control_in_test = 0;
 
// What is the expected packet size?
static int control_in_test_packet_size = 0;
 
// How many packets have been transferred so far?
static int control_in_packets_transferred = 0;
 
// Cancel a control-in test. handle_test_control_in() will have updated the static
// control_in_test so that handle_reserved_control_messages() knows what to do.
// If the test is not actually going to be run then system consistency demands
// that this update be undone. Also, the endpoint will have been locked to
// detect concurrent tests on the control endpoint.
static void
cancel_test_control_in(UsbTest* test)
{
CYG_ASSERTC(test == control_in_test);
control_in_test = (UsbTest*) 0;
control_in_test_packet_size = 0;
control_in_packets_transferred = 0;
unlock_endpoint(0, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
test->cancel_fn = (void (*)(UsbTest*)) 0;
}
 
// Prepare for a control-IN transfer test.
static usbs_control_return
handle_test_control_in(usb_devreq* req)
{
UsbTest* test;
int index = 0;
 
CYG_ASSERTC((UsbTest*)0 == control_in_test);
test = pool_allocate();
unpack_usbtest_control_in(&(test->test_params.control_in), class_request, &index);
 
lock_endpoint(0, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
test->which_test = usbtest_control_in;
test->recovery.endpoint = 0;
test->recovery.protocol = USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL;
test->recovery.size = 0; // Does not actually matter
test->cancel_fn = &cancel_test_control_in;
 
// Assume a pass. Failures are easy to detect.
test->result_pass = 1;
control_in_test = test;
control_in_test_packet_size = test->test_params.control_in.packet_size_initial;
control_in_packets_transferred = 0;
 
return USBS_CONTROL_RETURN_HANDLED;
}
// The thread for a control-in test. Actually all the hard work is done at DSR
// level, so this thread serves simply to detect when the test has completed
// and to perform some clean-ups.
static void
run_test_control_in(UsbTest* test)
{
CYG_ASSERTC(test == control_in_test);
cyg_semaphore_wait(&(test->sem));
 
// The DSR has detected that the test is complete.
control_in_test = (UsbTest*) 0;
control_in_test_packet_size = 0;
control_in_packets_transferred = 0;
test->cancel_fn = (void (*)(UsbTest*)) 0;
unlock_endpoint(0, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
}
 
// ----------------------------------------------------------------------------
// This is installed from inside main() as the handler for reserved
// control messages.
static usbs_control_return
handle_reserved_control_messages(usbs_control_endpoint* endpoint, void* data)
{
usb_devreq* req = (usb_devreq*) endpoint->control_buffer;
usbs_control_return result;
 
CYG_ASSERT(endpoint == control_endpoint, "control endpoint mismatch");
switch(req->request) {
case USBTEST_RESERVED_CONTROL_IN:
{
unsigned char* buf;
int len;
if ((UsbTest*)0 == control_in_test) {
result = USBS_CONTROL_RETURN_STALL;
break;
}
 
// Is this test over? If so indicate a failure because we
// cannot have received all the control packets.
if (current_tests_terminated) {
control_in_test->result_pass = 0;
snprintf(control_in_test->result_message, USBTEST_MAX_MESSAGE,
"Target, control IN transfer: not all packets received.");
cyg_semaphore_post(&(control_in_test->sem));
control_in_test = (UsbTest*) 0;
result = USBS_CONTROL_RETURN_STALL;
break;
}
// A control-IN test is indeed in progress, and the current state is
// held in control_in_test and control_in_test_packet_size. Check that
// the packet size matches up, i.e. that host and target are in sync.
len = (req->length_hi << 8) || req->length_lo;
if (control_in_test_packet_size != len) {
control_in_test->result_pass = 0;
snprintf(control_in_test->result_message, USBTEST_MAX_MESSAGE,
"Target, control IN transfer on endpoint %d : the host only requested %d bytes instead of %d",
len, control_in_test_packet_size);
cyg_semaphore_post(&(control_in_test->sem));
control_in_test = (UsbTest*) 0;
result = USBS_CONTROL_RETURN_STALL;
break;
}
 
// Prepare a suitable reply buffer. This is happening at
// DSR level so runtime is important, but with an upper
// bound of 255 bytes the buffer should be small enough.
buf = control_in_test->buffer;
usbtest_fill_buffer(&(control_in_test->test_params.control_in.data), buf, control_in_test_packet_size);
control_endpoint->buffer_size = control_in_test_packet_size;
control_endpoint->buffer = buf;
USBTEST_CONTROL_NEXT_PACKET_SIZE(control_in_test_packet_size, control_in_test->test_params.control_in);
 
// Have all the packets been transferred?
control_in_packets_transferred++;
if (control_in_packets_transferred == control_in_test->test_params.control_in.number_packets) {
cyg_semaphore_post(&(control_in_test->sem));
control_in_test = (UsbTest*) 0;
}
result = USBS_CONTROL_RETURN_HANDLED;
break;
}
default:
CYG_FAIL("Unexpected reserved control message");
break;
}
return result;
}
 
/*}}}*/
 
// FIXME: add more tests.
 
// This utility is invoked from a thread in the thread pool whenever there is
// work to be done. It simply dispatches to the appropriate handler.
static void
run_test(UsbTest* test)
{
switch(test->which_test)
{
case usbtest_bulk_out : run_test_bulk_out(test); break;
case usbtest_bulk_in : run_test_bulk_in(test); break;
case usbtest_control_in: run_test_control_in(test); break;
default:
CYG_TEST_FAIL_EXIT("Internal error, attempt to run unknown test.\n");
break;
}
}
 
/*}}}*/
/*{{{ The thread pool */
 
// ----------------------------------------------------------------------------
// Just like on the host side, it is desirable to have a pool of
// threads available to perform test operations. Strictly speaking
// some tests will run without needing a separate thread, since many
// operations can be performed at DSR level. However typical
// application code will involve threads and it is desirable for test
// code to behave the same way. Also, some operations like validating
// the transferred data are expensive, and best done in thread context.
 
typedef struct PoolEntry {
cyg_sem_t wakeup;
cyg_thread thread_data;
cyg_handle_t thread_handle;
char thread_name[16];
char thread_stack[2 * CYGNUM_HAL_STACK_SIZE_TYPICAL];
cyg_bool in_use;
cyg_bool running;
UsbTest test;
} PoolEntry;
 
// This array must be uninitialized, or the executable size would
// be ludicrous.
PoolEntry pool[USBTEST_MAX_CONCURRENT_TESTS];
 
// The entry point for every thread in the pool. It just loops forever,
// waiting until it is supposed to run a test.
static void
pool_thread_function(cyg_addrword_t arg)
{
PoolEntry* pool_entry = (PoolEntry*) arg;
 
for ( ; ; ) {
cyg_semaphore_wait(&(pool_entry->wakeup));
run_test(&(pool_entry->test));
pool_entry->running = 0;
}
}
 
// Initialize all threads in the pool.
static void
pool_initialize(void)
{
int i;
for (i = 0; i < USBTEST_MAX_CONCURRENT_TESTS; i++) {
cyg_semaphore_init(&(pool[i].wakeup), 0);
pool[i].in_use = 0;
pool[i].running = 0;
sprintf(pool[i].thread_name, "worker%d", i);
cyg_thread_create( 0, &pool_thread_function, (cyg_addrword_t) &(pool[i]),
pool[i].thread_name, pool[i].thread_stack, 2 * CYGNUM_HAL_STACK_SIZE_TYPICAL,
&(pool[i].thread_handle), &(pool[i].thread_data));
cyg_thread_resume(pool[i].thread_handle);
}
}
 
// Allocate a single entry in the thread pool
static UsbTest*
pool_allocate(void)
{
UsbTest* result = (UsbTest*) 0;
 
if (thread_counter == USBTEST_MAX_CONCURRENT_TESTS) {
CYG_TEST_FAIL_EXIT("Internal error, thread resources exhaused.\n");
}
result = &(pool[thread_counter].test);
thread_counter++;
reset_usbtest(result);
return result;
}
 
// Start all the threads that are supposed to be running tests.
static void
pool_start(void)
{
int i;
for (i = 0; i < thread_counter; i++) {
pool[i].running = 1;
cyg_semaphore_post(&(pool[i].wakeup));
}
}
 
/*}}}*/
/*{{{ Class control messages */
 
// ----------------------------------------------------------------------------
// Handle class control messages. These provide the primary form of
// communication between host and target. There are requests to find out
// the number of endpoints, details of each endpoint, prepare a test run,
// abort a test run, get status, terminate the target-side, and so on.
// The handlers for starting specific test cases are kept alongside
// the test cases themselves.
//
// Note that these handlers will typically be invoked from DSR context
// and hence they are subject to the usual DSR restrictions.
//
// Problems have been experienced in some hosts sending control messages
// that involve additional host->target data. An ugly workaround is
// in place whereby any such data is sent in advance using separate
// control messages.
 
/*{{{ endpoint count */
 
// How many endpoints are supported by this device? That information is
// determined during initialization.
static usbs_control_return
handle_endpoint_count(usb_devreq* req)
{
CYG_ASSERTC((1 == req->length_lo) && (0 == req->length_hi) && \
((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
class_reply[0] = (unsigned char) number_endpoints;
control_endpoint->buffer = class_reply;
control_endpoint->buffer_size = 1;
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ endpoint details */
 
// The host wants to know the details of a specific USB endpoint.
// The format is specified in protocol.h
static usbs_control_return
handle_endpoint_details(usb_devreq* req)
{
int buf_index;
 
CYG_ASSERTC((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN);
CYG_ASSERTC((USBTEST_MAX_CONTROL_DATA == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC(req->index_lo < number_endpoints);
CYG_ASSERTC((0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
class_reply[0] = (unsigned char) usbs_testing_endpoints[req->index_lo].endpoint_type;
class_reply[1] = (unsigned char) usbs_testing_endpoints[req->index_lo].endpoint_number;
class_reply[2] = (unsigned char) usbs_testing_endpoints[req->index_lo].endpoint_direction;
class_reply[3] = (unsigned char) usbs_testing_endpoints[req->index_lo].max_in_padding;
buf_index = 4;
pack_int(usbs_testing_endpoints[req->index_lo].min_size, class_reply, &buf_index);
pack_int(usbs_testing_endpoints[req->index_lo].max_size, class_reply, &buf_index);
if (NULL == usbs_testing_endpoints[req->index_lo].devtab_entry) {
class_reply[buf_index] = '\0';
control_endpoint->buffer_size = buf_index + 1;
} else {
int len = strlen(usbs_testing_endpoints[req->index_lo].devtab_entry) + buf_index + 1;
if (len > USBTEST_MAX_CONTROL_DATA) {
return USBS_CONTROL_RETURN_STALL;
} else {
strcpy(&(class_reply[buf_index]), usbs_testing_endpoints[req->index_lo].devtab_entry);
control_endpoint->buffer_size = len;
}
}
control_endpoint->buffer = class_reply;
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ sync */
 
// The host wants to know whether or not the target is currently busy doing
// stuff. This information is held in a static.
static usbs_control_return
handle_sync(usb_devreq* req)
{
CYG_ASSERTC((1 == req->length_lo) && (0 == req->length_hi) && \
((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(0 == class_request_size, "A sync operation should not involve any data");
class_reply[0] = (unsigned char) idle;
control_endpoint->buffer = class_reply;
control_endpoint->buffer_size = 1;
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ pass/fail */
 
// Allow the host to generate some pass or fail messages, and
// optionally terminate the test. These are synchronous requests
// so the data can be left in class_request.
 
static int passfail_request = 0;
 
// Invoked from thread context
static void
handle_passfail_action(void)
{
switch (passfail_request) {
case USBTEST_PASS:
CYG_TEST_PASS(class_request);
break;
case USBTEST_PASS_EXIT:
CYG_TEST_PASS(class_request);
CYG_TEST_EXIT("Exiting normally as requested by the host");
break;
case USBTEST_FAIL:
CYG_TEST_FAIL(class_request);
break;
case USBTEST_FAIL_EXIT:
CYG_TEST_FAIL(class_request);
CYG_TEST_EXIT("Exiting normally as requested by the host");
break;
default:
CYG_FAIL("Bogus invocation of usbtest_main_passfail");
break;
}
}
 
// Invoked from DSR context
static usbs_control_return
handle_passfail(usb_devreq* req)
{
CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(class_request_size > 0, "A pass/fail message should be supplied");
CYG_ASSERT(idle, "Pass/fail messages are only allowed when idle");
CYG_ASSERT((void (*)(void))0 == main_thread_action, "No thread operation should be pending.");
passfail_request = req->request;
idle = false;
main_thread_action = &handle_passfail_action;
cyg_semaphore_post(&main_wakeup);
 
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ abort */
 
// The host has concluded that there is no easy way to get both target and
// host back to a sensible state. For example there may be a thread that
// is blocked waiting for some I/O that is not going to complete. The abort
// should be handled at thread level, not DSR level, so that the host
// still sees the low-level USB handshake.
 
static void
handle_abort_action(void)
{
CYG_TEST_FAIL_EXIT("Test abort requested by host application");
}
 
static usbs_control_return
handle_abort(usb_devreq* req)
{
CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(idle, "Abort messages are only allowed when idle");
CYG_ASSERT((void (*)(void))0 == main_thread_action, "No thread operation should be pending.");
idle = false;
main_thread_action = &handle_abort_action;
cyg_semaphore_post(&main_wakeup);
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ cancel */
 
// Invoked from thread context
// Cancelling pending test cases simply involves iterating over the allocated
// entries in the pool, invoking any cancellation functions that have been
// defined, and then resetting the tread count. The actual tests have not
// yet started so none of the threads will be active.
static void
handle_cancel_action(void)
{
int i;
for (i = 0; i < thread_counter; i++) {
if ((void (*)(UsbTest*))0 != pool[i].test.cancel_fn) {
(*(pool[i].test.cancel_fn))(&(pool[i].test));
pool[i].test.cancel_fn = (void (*)(UsbTest*)) 0;
}
}
thread_counter = 0;
}
 
// Invoked from DSR context
static usbs_control_return
handle_cancel(usb_devreq* req)
{
CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(0 == class_request_size, "A cancel operation should not involve any data");
CYG_ASSERT(idle, "Cancel requests are only allowed when idle");
CYG_ASSERT(!running, "Cancel requests cannot be sent once the system is running");
CYG_ASSERT((void (*)(void))0 == main_thread_action, "No thread operation should be pending.");
idle = false;
main_thread_action = &handle_cancel_action;
cyg_semaphore_post(&main_wakeup);
 
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ start */
 
// Start the tests running. This just involves waking up the pool threads
// and setting the running flag, with the latter serving primarily for
// assertions.
 
static usbs_control_return
handle_start(usb_devreq* req)
{
CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(0 == class_request_size, "A start operation should not involve any data");
CYG_ASSERT(!running, "Start requests cannot be sent if the system is already running");
 
current_tests_terminated = false;
running = true;
pool_start();
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ finished */
 
// Have all the tests finished? This involves checking all the threads
// involved in the current batch of tests and seeing whether or not
// their running flag is still set.
 
static usbs_control_return
handle_finished(usb_devreq* req)
{
int i;
int result = 1;
CYG_ASSERTC((1 == req->length_lo) && (0 == req->length_hi) && \
((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(0 == class_request_size, "A finished operation should not involve any data");
CYG_ASSERT(running, "Finished requests can only be sent if the system is already running");
for (i = 0; i < thread_counter; i++) {
if (pool[i].running) {
result = 0;
break;
}
}
class_reply[0] = (unsigned char) result;
control_endpoint->buffer = class_reply;
control_endpoint->buffer_size = 1;
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ set terminated */
 
// A timeout has occurred, or there is some other failure. The first step
// in recovery is to set the terminated flag so that as recovery action
// takes place and the threads wake up they make no attempt to continue
// doing more transfers.
 
static usbs_control_return
handle_set_terminated(usb_devreq* req)
{
CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(0 == class_request_size, "A set-terminated operation should not involve any data");
CYG_ASSERT(running, "The terminated flag can only be set when there are running tests");
 
current_tests_terminated = 1;
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ get recovery */
 
// Return the recovery information for one of the threads involved in the
// current batch of tests, so that the host can perform a USB operation
// that will sort out that thread.
static usbs_control_return
handle_get_recovery(usb_devreq* req)
{
int buffer_index;
CYG_ASSERT(current_tests_terminated, "Recovery should only be attempted when the terminated flag is set");
CYG_ASSERT(running, "If there are no tests running then recovery is impossible");
CYG_ASSERTC((12 == req->length_lo) && (0 == req->length_hi) && \
((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
CYG_ASSERTC(req->index_lo <= thread_counter);
CYG_ASSERTC((0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(0 == class_request_size, "A get-recovery operation should not involve any data");
 
control_endpoint->buffer = class_reply;
if (!pool[req->index_lo].running) {
// Actually, this particular thread has terminated so no recovery is needed.
control_endpoint->buffer_size = 0;
} else {
buffer_index = 0;
pack_usbtest_recovery(&(pool[req->index_lo].test.recovery), class_reply, &buffer_index);
control_endpoint->buffer_size = buffer_index;
}
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ perform recovery */
 
// The host has identified a course of action that could unlock a thread
// on the host-side that is currently blocked performing a USB operation.
// Typically this involves either sending or accepting some data. If the
// endpoint is still locked, in other words if there is a still a local
// thread attempting to communicate on the specified endpoint, then
// things are messed up: both sides are trying to communicate, but nothing
// is happening. The eCos USB API is such that attempting multiple
// concurrent operations on a single endpoint is disallowed, so
// the recovery request has to be ignored. If things do not sort themselves
// out then the whole test run will have to be aborted.
 
// A dummy completion function for when a recovery operation has completed.
static void
recovery_callback(void* callback_arg, int transferred)
{
CYG_UNUSED_PARAM(void*, callback_arg);
CYG_UNUSED_PARAM(int, transferred);
}
static usbs_control_return
handle_perform_recovery(usb_devreq* req)
{
int buffer_index;
int endpoint_number;
int endpoint_direction;
UsbTest_Recovery recovery;
CYG_ASSERT(current_tests_terminated, "Recovery should only be attempted when the terminated flag is set");
CYG_ASSERT(running, "If there are no tests running then recovery is impossible");
CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(12 == class_request_size, "A perform-recovery operation requires recovery data");
 
buffer_index = 0;
unpack_usbtest_recovery(&recovery, class_request, &buffer_index);
endpoint_number = recovery.endpoint & ~USB_DEVREQ_DIRECTION_MASK;
endpoint_direction = recovery.endpoint & USB_DEVREQ_DIRECTION_MASK;
 
if (!is_endpoint_locked(endpoint_number, endpoint_direction)) {
// Locking the endpoint here would be good, but the endpoint would then
// have to be unlocked again - probably in the recovery callback.
// This complication is ignored for now.
 
if (USB_ENDPOINT_DESCRIPTOR_ATTR_BULK == recovery.protocol) {
int ep_index = lookup_endpoint(endpoint_number, endpoint_direction, USB_ENDPOINT_DESCRIPTOR_ATTR_BULK);
CYG_ASSERTC(-1 != ep_index);
 
if (USB_DEVREQ_DIRECTION_IN == endpoint_direction) {
// The host wants some data. Supply it. A single byte will do fine to
// complete the transfer.
usbs_start_tx_buffer((usbs_tx_endpoint*) usbs_testing_endpoints[ep_index].endpoint,
recovery_buffer, 1, &recovery_callback, (void*) 0);
} else {
// The host is trying to send some data. Accept all of it.
usbs_start_rx_buffer((usbs_rx_endpoint*) usbs_testing_endpoints[ep_index].endpoint,
recovery_buffer, recovery.size, &recovery_callback, (void*) 0);
}
}
 
// No support for isochronous or interrupt transfers yet.
// handle_reserved_control_messages() should generate stalls which
// have the desired effect.
}
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ get result */
 
// Return the result of one the tests. This can be a single byte for
// a pass, or a single byte plus a message for a failure.
 
static usbs_control_return
handle_get_result(usb_devreq* req)
{
CYG_ASSERTC((USBTEST_MAX_CONTROL_DATA == req->length_lo) && (0 == req->length_hi) && \
((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
CYG_ASSERTC(req->index_lo <= thread_counter);
CYG_ASSERTC((0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(0 == class_request_size, "A get-result operation should not involve any data");
CYG_ASSERT(running, "Results can only be sent if a run is in progress");
CYG_ASSERT(!pool[req->index_lo].running, "Cannot request results for a test that has not completed");
 
class_reply[0] = pool[req->index_lo].test.result_pass;
if (class_reply[0]) {
control_endpoint->buffer_size = 1;
} else {
strncpy(&(class_reply[1]), pool[req->index_lo].test.result_message, USBTEST_MAX_CONTROL_DATA - 2);
class_reply[USBTEST_MAX_CONTROL_DATA - 1] = '\0';
control_endpoint->buffer_size = 1 + strlen(&(class_reply[1])) + 1;
}
control_endpoint->buffer = class_reply;
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ batch done */
 
// A batch of test has been completed - at least, the host thinks so.
// If the host is correct then all that is required here is to reset
// the thread pool and clear the global running flag - that is sufficient
// to allow a new batch of tests to be started.
 
static usbs_control_return
handle_batch_done(usb_devreq* req)
{
int i;
CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
CYG_ASSERT(0 == class_request_size, "A batch-done operation should not involve any data");
CYG_ASSERT(running, "There must be a current batch of tests");
 
for (i = 0; i < thread_counter; i++) {
CYG_ASSERTC(!pool[i].running);
}
thread_counter = 0;
running = false;
return USBS_CONTROL_RETURN_HANDLED;
 
}
 
/*}}}*/
/*{{{ verbosity */
 
static usbs_control_return
handle_verbose(usb_devreq* req)
{
CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi));
CYG_ASSERT(0 == class_request_size, "A set-verbosity operation should not involve any data");
 
verbose = (req->value_hi << 8) + req->value_lo;
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ initialise bulk out endpoint */
 
// ----------------------------------------------------------------------------
// Accept an initial endpoint on a bulk endpoint. This avoids problems
// on some hardware such as the SA11x0 which can start to accept data
// before the software is ready for it.
 
static void handle_init_callback(void* arg, int result)
{
idle = true;
}
 
static usbs_control_return
handle_init_bulk_out(usb_devreq* req)
{
static char buf[64];
int ep_index;
usbs_rx_endpoint* endpoint;
CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi));
CYG_ASSERTC((0 == req->value_hi) && (0 < req->value_lo) && (req->value_lo < 16));
CYG_ASSERT(0 == class_request_size, "An init_bulk_out operation should not involve any data");
 
ep_index = lookup_endpoint(req->value_lo, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, USB_ENDPOINT_DESCRIPTOR_ATTR_BULK);
CYG_ASSERTC(-1 != ep_index);
endpoint = (usbs_rx_endpoint*) usbs_testing_endpoints[ep_index].endpoint;
idle = false;
usbs_start_rx_buffer(endpoint, buf, 64, &handle_init_callback, (void*) 0);
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
/*{{{ additional control data */
 
// Accumulate some more data in the control buffer, ahead of an upcoming
// request.
static usbs_control_return
handle_control_data(usb_devreq* req)
{
class_request[class_request_size + 0] = req->value_hi;
class_request[class_request_size + 1] = req->value_lo;
class_request[class_request_size + 2] = req->index_hi;
class_request[class_request_size + 3] = req->index_lo;
 
switch(req->request) {
case USBTEST_CONTROL_DATA1 : class_request_size += 1; break;
case USBTEST_CONTROL_DATA2 : class_request_size += 2; break;
case USBTEST_CONTROL_DATA3 : class_request_size += 3; break;
case USBTEST_CONTROL_DATA4 : class_request_size += 4; break;
}
 
return USBS_CONTROL_RETURN_HANDLED;
}
 
/*}}}*/
 
typedef struct class_handler {
int request;
usbs_control_return (*handler)(usb_devreq*);
} class_handler;
static class_handler class_handlers[] = {
{ USBTEST_ENDPOINT_COUNT, &handle_endpoint_count },
{ USBTEST_ENDPOINT_DETAILS, &handle_endpoint_details },
{ USBTEST_PASS, &handle_passfail },
{ USBTEST_PASS_EXIT, &handle_passfail },
{ USBTEST_FAIL, &handle_passfail },
{ USBTEST_FAIL_EXIT, &handle_passfail },
{ USBTEST_SYNCH, &handle_sync },
{ USBTEST_ABORT, &handle_abort },
{ USBTEST_CANCEL, &handle_cancel },
{ USBTEST_START, &handle_start },
{ USBTEST_FINISHED, &handle_finished },
{ USBTEST_SET_TERMINATED, &handle_set_terminated },
{ USBTEST_GET_RECOVERY, &handle_get_recovery },
{ USBTEST_PERFORM_RECOVERY, &handle_perform_recovery },
{ USBTEST_GET_RESULT, &handle_get_result },
{ USBTEST_BATCH_DONE, &handle_batch_done },
{ USBTEST_VERBOSE, &handle_verbose },
{ USBTEST_INIT_BULK_OUT, &handle_init_bulk_out },
{ USBTEST_TEST_BULK, &handle_test_bulk },
{ USBTEST_TEST_CONTROL_IN, &handle_test_control_in },
{ USBTEST_CONTROL_DATA1, &handle_control_data },
{ USBTEST_CONTROL_DATA2, &handle_control_data },
{ USBTEST_CONTROL_DATA3, &handle_control_data },
{ USBTEST_CONTROL_DATA4, &handle_control_data },
{ -1, (usbs_control_return (*)(usb_devreq*)) 0 }
};
 
static usbs_control_return
handle_class_control_messages(usbs_control_endpoint* endpoint, void* data)
{
usb_devreq* req = (usb_devreq*) endpoint->control_buffer;
int request = req->request;
usbs_control_return result;
int i;
 
VERBOSE(3, "Received control message %02x\n", request);
CYG_ASSERT(endpoint == control_endpoint, "control endpoint mismatch");
result = USBS_CONTROL_RETURN_UNKNOWN;
for (i = 0; (usbs_control_return (*)(usb_devreq*))0 != class_handlers[i].handler; i++) {
if (request == class_handlers[i].request) {
result = (*(class_handlers[i].handler))(req);
if ((USBTEST_CONTROL_DATA1 != request) &&
(USBTEST_CONTROL_DATA2 != request) &&
(USBTEST_CONTROL_DATA3 != request) &&
(USBTEST_CONTROL_DATA4 != request)) {
// Reset the request data buffer after all normal requests.
class_request_size = 0;
}
break;
}
}
CYG_UNUSED_PARAM(void*, data);
if (USBS_CONTROL_RETURN_HANDLED != result) {
VERBOSE(1, "Control message %02x not handled\n", request);
}
return result;
}
 
/*}}}*/
/*{{{ main() */
 
// ----------------------------------------------------------------------------
// Initialization.
int
main(int argc, char** argv)
{
int i;
 
CYG_TEST_INIT();
 
// The USB device driver should have provided an array of endpoint
// descriptors, usbs_testing_endpoints(). One entry in this array
// should be a control endpoint, which is needed for initialization.
// It is also useful to know how many endpoints there are.
for (i = 0; !USBS_TESTING_ENDPOINTS_IS_TERMINATOR(usbs_testing_endpoints[i]); i++) {
if ((0 == usbs_testing_endpoints[i].endpoint_number) &&
(USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL== usbs_testing_endpoints[i].endpoint_type)) {
CYG_ASSERT((usbs_control_endpoint*)0 == control_endpoint, "There should be only one control endpoint");
control_endpoint = (usbs_control_endpoint*) usbs_testing_endpoints[i].endpoint;
}
}
if ((usbs_control_endpoint*)0 == control_endpoint) {
CYG_TEST_FAIL_EXIT("Unable to find a USB control endpoint");
}
number_endpoints = i;
CYG_ASSERT(number_endpoints <= USBTEST_MAX_ENDPOINTS, "impossible number of endpoints");
 
// Some of the information provided may not match the actual capabilities
// of the testing code, e.g. max_size limits.
fix_driver_endpoint_data();
// This semaphore is used for communication between the DSRs that process control
// messages and the main thread
cyg_semaphore_init(&main_wakeup, 0);
 
// Take care of the pool of threads and related data.
pool_initialize();
 
// Start the heartbeat thread, to make sure that the gdb session stays
// alive.
start_heartbeat();
// Now it is possible to start up the USB device driver. The host can detect
// this, connect, get the enumeration data, and then testing will proceed
// in response to class control messages.
provide_endpoint_enumeration_data();
control_endpoint->enumeration_data = &usb_enum_data;
control_endpoint->class_control_fn = &handle_class_control_messages;
control_endpoint->reserved_control_fn = &handle_reserved_control_messages;
usbs_start(control_endpoint);
 
// Now it is over to the host to detect this target and start performing tests.
// Much of this is handled at DSR level, in response to USB control messages.
// Some of those control messages require action at thread level, and that is
// achieved by signalling a semaphore and waking up this thread. A static
// function pointer is used to keep track of what operation is actually required.
for (;;) {
void (*handler)(void);
cyg_semaphore_wait(&main_wakeup);
handler = main_thread_action;
main_thread_action = 0;
CYG_CHECK_FUNC_PTR(handler, "Main thread woken up when there is nothing to be done");
(*handler)();
idle = true;
}
}
 
/*}}}*/
/protocol.h
0,0 → 1,194
//=================================================================
//
// protocol.h
//
// USB testing - host<->target protocol
//
//==========================================================================
//####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####
//
// This header file is shared between target and host, and serves to
// define certain aspects of the protocol used between the two such
// as request codes.
//
// Author(s): bartv
// Date: 2001-07-04
//####DESCRIPTIONEND####
//==========================================================================
 
// The largest control packet that will be sent or expected.
#define USBTEST_MAX_CONTROL_DATA 255
 
// The largest error message that can be sent.
#define USBTEST_MAX_MESSAGE 254
 
// The largest bulk transfer that will be sent or expected. Because of
// the use of the USB devfs support in the Linux kernel this is
// currently limited to a single page, i.e. 4096 bytes. To allow for
// padding, it is actually reduced to a slightly smaller size of 4090
// bytes. This should still be sufficient to test most interesting
// boundary conditions, apart from the transition to >64K.
//
// A small amount of additional buffer space should be allocated by
// both host and target to allow for padding and possibly cache
// alignment. All other protocols involve smaller transfers than this,
// <= 64 bytes for interrupt transfers, <= 1023 for isochronous.
#define USBTEST_MAX_BULK_DATA (4096)
#define USBTEST_MAX_BULK_DATA_EXTRA 1024
 
// The maximum number of tests that can be run concurrently. Each
// needs a separate thread, stack, and buffer so there are memory
// consumption implications.
#define USBTEST_MAX_CONCURRENT_TESTS 8
 
// Allow the host to find out the number of endpoints supported on
// this target. The theoretical maximum number of endpoints is 91
// (endpoint 0 control, endpoint 1-15 for both IN and OUT bulk, iso
// and interrupt) so a single byte response will suffice. The value
// and index fields are not used.
#define USBTEST_MAX_ENDPOINTS 91
#define USBTEST_ENDPOINT_COUNT 0x001
 
// Get hold of additional information about a specific entry in the
// array of endpoint details. The index field in the request
// identifies the entry of interest. The reply information is as per
// the usbs_testing_endpoint structure, and consists of:
// 1) one byte, the endpoint type (control, bulk, ...)
// 2) one byte, the endpoint number (as opposed to the array index number)
// 3) one byte for direction, USB_DIR_IN or USB_DIR_OUT
// 4) one byte for max_in_padding, usually 0
// 5) four bytes for min_size, 32-bit little-endian integer
// 6) four bytes for max_size, 32-bit little-endian integer
// 7) an additional n bytes for the devtab name, max ~240 bytes
// although usually far less.
#define USBTEST_ENDPOINT_DETAILS 0x002
 
// Report pass or failure. The host will send a string of up to
// MAX_CONTROL_DATA bytes. The value and index fields are not used.
#define USBTEST_PASS 0x003
#define USBTEST_PASS_EXIT 0x004
#define USBTEST_FAIL 0x005
#define USBTEST_FAIL_EXIT 0x006
 
// Synchronise. One problem with the current eCos USB API is that
// there is no way to have a delayed response to a control message.
// Any such support would be tricky, there are significant differences
// in the hardware implementations and also timing constraints that
// need to be satisfied. Instead the entire response to any control
// request has to be prepared at DSR level. Usually this does not
// cause any problems, e.g. for handling the standard control
// messages, but for USB testing it may not be possible to handle a
// request entirely at DSR level - yet the next full request should
// not come in until the current one has been handled at thread-level.
// To work around this there is support for a synchronization control
// message. The return value is a single byte, 1 if the target is
// ready for new requests, 0 if there is a still a request being
// processed. The host can then perform some polling.
#define USBTEST_SYNCH 0x007
 
// Abort. There is no easy way to get both host and target back to a
// known state, so abort the current test run.
#define USBTEST_ABORT 0x008
 
// Cancel the current batch of tests. Something has gone wrong at the
// Tcl level, so any tests already prepared must be abandoned. No
// additional data is required.
#define USBTEST_CANCEL 0x009
 
// Start the current batch of tests. No additional data is involved
// or expected.
#define USBTEST_START 0x00A
 
// Has the current batch of tests finished? The host side polls the
// target at regular intervals for this information.
#define USBTEST_FINISHED 0x00B
 
// Set the test-terminated flag. Something has gone wrong, probably a
// timeout.
#define USBTEST_SET_TERMINATED 0x00C
 
// Get hold of recovery information for thread i in the target, where
// the index field of the request identifies the thread. The result
// is zero-bytes if the specified test has already finished, otherwise
// a recovery structure.
#define USBTEST_GET_RECOVERY 0x00D
 
// The target should perform a recovery action to unlock a thread
// on the host. The request holds a recovery structure.
#define USBTEST_PERFORM_RECOVERY 0x00E
 
// Collect the test result. The result is a single byte that indicates
// pass or fail, optionally followed by a failure message.
#define USBTEST_GET_RESULT 0x00F
 
// The current batch of tests has completed. Perform any final clean-ups.
#define USBTEST_BATCH_DONE 0x010
 
// Set the verbosity level on the target-side
#define USBTEST_VERBOSE 0x011
 
// Perform endpoint initialization to ensure host and target
// can actually communicate over a given endpoint
#define USBTEST_INIT_CONTROL 0x012
#define USBTEST_INIT_BULK_IN 0x013
#define USBTEST_INIT_BULK_OUT 0x014
#define USBTEST_INIT_ISO_IN 0x015
#define USBTEST_INIT_ISO_OUT 0x016
#define USBTEST_INIT_INTERRUPT_IN 0x017
#define USBTEST_INIT_INTERRUPT_OUT 0x018
 
 
// A standard bulk test. The data consists of a UsbTest_Bulk
// structure, suitably packed.
#define USBTEST_TEST_BULK 0x040
 
// A control-IN test. The host will send reserved control messages with
// an appropriate length field, and the target should return that data.
#define USBTEST_TEST_CONTROL_IN 0x041
 
// Sub-protocols for reserved control messages, supporting test operations
// other than control-IN.
#define USBTEST_RESERVED_CONTROL_IN 0x01
 
// Work around a problem with control messages that involve additional
// data from host to target. This problem is not yet well-understood.
// The workaround involves sending multiple control packets with
// up to four bytes encoded in the index and value fields.
#define USBTEST_CONTROL_DATA1 0x0F1
#define USBTEST_CONTROL_DATA2 0x0F2
#define USBTEST_CONTROL_DATA3 0x0F3
#define USBTEST_CONTROL_DATA4 0x0F4
 
/common.c
0,0 → 1,681
/*{{{ Banner */
 
/*=================================================================
//
// common.c
//
// USB testing - code common to host and target
//
//==========================================================================
//####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####
//
// This module contains some definitions and functions that are common to
// both the host and target side of USB testing, for example filling in
// a buffer with well-known data and validating the contents at the other end.
// The module is #include'd by other code rather than compiled separately,
// which simplifies the build process.
//
// Author(s): bartv
// Date: 2001-08-14
//####DESCRIPTIONEND####
//==========================================================================
*/
 
/*}}}*/
 
/*{{{ Simple data pack and unpack operations */
 
// ----------------------------------------------------------------------------
// Utilities to pack and unpack data into buffers.
//
// Integers are transferred with 32 bits of precision, irrespective
// of the capabilities of either target and host.
 
static inline void
pack_int(int datum, unsigned char* buffer, int* index_ptr)
{
int index = *index_ptr;
buffer[index++] = (datum >> 0) & 0x0FF;
buffer[index++] = (datum >> 8) & 0x0FF;
buffer[index++] = (datum >> 16) & 0x0FF;
buffer[index++] = (datum >> 24) & 0x0FF;
*index_ptr = index;
}
 
static inline int
unpack_int(unsigned char* buffer, int* index_ptr)
{
int index = *index_ptr;
int result;
 
result = (buffer[index++] << 0);
result |= (buffer[index++] << 8);
result |= (buffer[index++] << 16);
result |= (buffer[index++] << 24);
*index_ptr = index;
return result;
}
 
/*}}}*/
/*{{{ Buffer data and validation */
 
// ----------------------------------------------------------------------------
// The data required for a given test. For some test cases, for
// example when trying to achieve maximum throughput, it does not
// matter what data is transferred. For other tests it is important to
// validate that the data sent and received match up, and there should
// be some control over the actual data: some tests might want to send
// a long sequence of byte 0, while others want to send more random data
// for which a simple random number generator is useful.
//
// Exactly the same routines are used on both host and target to fill in
// and check buffers, and they are sufficiently simple that the routines
// should get compiled in compatible ways.
//
// There is no support at present for sending specific data, e.g. a
// specific ethernet packet that appears to be causing problems. Knowledge
// of specific data cannot be compiled into the test code, so the only
// way to implement something like this would be to transfer the
// problematical data over the USB bus in order to determine whether or
// not the bus is capable of reliably transferring this data. That is
// not entirely impossible (checksums, use of alternative endpoints),
// but it is not implemented.
//
// An alternative approach would be to support a bounce operation
// involving both an IN and an OUT endpoint, doing validation only on
// the host. Again that is not yet implemented.
//
// The byte_fill and int_fill options are actually redundant because the
// same effect can be achieved using a multiplier of 1 and an increment
// of 0, but they can be implemented much more efficiently so may be
// useful for benchmarks.
 
typedef enum usbtestdata {
usbtestdata_none = 0, // There is nothing useful in the data
usbtestdata_bytefill = 1, // The data consists of a single byte, repeated
usbtestdata_wordfill = 2, // Or a single integer
usbtestdata_byteseq = 3, // Or a pseudo-random sequence (a * seed) + b
usbtestdata_wordseq = 4 // as either bytes or integers
} usbtestdata;
 
typedef struct UsbTestData {
usbtestdata format;
int seed;
int multiplier; // 1103515245
int increment; // 12345
int transfer_seed_multiplier;
int transfer_seed_increment;
int transfer_multiplier_multiplier;
int transfer_multiplier_increment;
int transfer_increment_multiplier;
int transfer_increment_increment;
} UsbTestData;
 
static void
usbtest_fill_buffer(UsbTestData* how, unsigned char* buffer, int length)
{
switch(how->format)
{
case usbtestdata_none:
return;
case usbtestdata_bytefill:
// Leave it to the system to optimise memset().
memset(buffer, (how->seed & 0x0FF), length);
break;
 
case usbtestdata_wordfill:
{
// The buffer may not be a multiple of four bytes, so the last entry is always
// zero'd.
int i;
int index = 0;
for (i = 0; i < (length / 4); i++) {
pack_int(how->seed, buffer, &index);
}
pack_int(0, buffer, &index);
break;
}
 
case usbtestdata_byteseq:
{
int i;
for (i = 0; i < length; i++) {
buffer[i] = (how->seed & 0x00FF);
how->seed *= how->multiplier;
how->seed += how->increment;
}
break;
}
 
case usbtestdata_wordseq:
{
int i;
int index = 0;
for (i = 0; i < (length / 4); i++) {
pack_int(how->seed, buffer, &index);
how->seed *= how->multiplier;
how->seed += how->increment;
}
pack_int(0, buffer, &index);
break;
}
}
 
// After each transfer update the seed, multiplier and increment
// ready for the next one.
how->seed *= how->transfer_seed_multiplier;
how->seed += how->transfer_seed_increment;
how->multiplier *= how->transfer_multiplier_multiplier;
how->multiplier += how->transfer_multiplier_increment;
how->increment *= how->transfer_increment_multiplier;
how->increment += how->transfer_increment_increment;
}
 
static int
usbtest_check_buffer(UsbTestData* how, unsigned char* buffer, int length)
{
int result = 1;
 
switch(how->format) {
case usbtestdata_none:
break;
 
case usbtestdata_bytefill:
{
int i;
result = 1;
for (i = 0; i < length; i++) {
if (buffer[i] != (how->seed & 0x00FF)) {
result = 0;
break;
}
}
break;
}
 
case usbtestdata_wordfill:
{
int i;
int index = 0;
for (i = 0; i < (length / 4); i++) {
int datum = unpack_int(buffer, &index);
if (datum != (how->seed & 0x0FFFFFFFF)) {
result = 0;
break;
}
}
for (i = 4 * i; result && (i < length); i++) {
if (0 != buffer[i]) {
result = 0;
break;
}
}
break;
}
 
case usbtestdata_byteseq:
{
int i;
for (i = 0; i < length; i++) {
if (buffer[i] != (how->seed & 0x00FF)) {
result = 0;
break;
}
how->seed *= how->multiplier;
how->seed += how->increment;
}
break;
}
 
case usbtestdata_wordseq:
{
int i;
int index = 0;
for (i = 0; i < (length / 4); i++) {
int datum = unpack_int(buffer, &index);
if (datum != (how->seed & 0x0FFFFFFFF)) {
result = 0;
break;
}
how->seed *= how->multiplier;
how->seed += how->increment;
}
for (i = 4 * i; result && (i < length); i++) {
if (0 != buffer[i]) {
result = 0;
break;
}
}
break;
}
}
 
// After each transfer update the seed, multiplier and increment
// ready for the next transfer.
how->seed *= how->transfer_seed_multiplier;
how->seed += how->transfer_seed_increment;
how->multiplier *= how->transfer_multiplier_multiplier;
how->multiplier += how->transfer_multiplier_increment;
how->increment *= how->transfer_increment_multiplier;
how->increment += how->transfer_increment_increment;
return result;
}
 
#ifdef HOST
static void
pack_usbtestdata(UsbTestData* data, unsigned char* buf, int* index)
{
pack_int((int)data->format, buf, index);
pack_int((int)data->seed, buf, index);
pack_int((int)data->multiplier, buf, index);
pack_int((int)data->increment, buf, index);
pack_int((int)data->transfer_seed_multiplier, buf, index);
pack_int((int)data->transfer_seed_increment, buf, index);
pack_int((int)data->transfer_multiplier_multiplier, buf, index);
pack_int((int)data->transfer_multiplier_increment, buf, index);
pack_int((int)data->transfer_increment_multiplier, buf, index);
pack_int((int)data->transfer_increment_increment, buf, index);
}
#endif
 
#ifdef TARGET
static void
unpack_usbtestdata(UsbTestData* data, unsigned char* buf, int* index)
{
data->format = (usbtestdata) unpack_int(buf, index);
data->seed = unpack_int(buf, index);
data->multiplier = unpack_int(buf, index);
data->increment = unpack_int(buf, index);
data->transfer_seed_multiplier = unpack_int(buf, index);
data->transfer_seed_increment = unpack_int(buf, index);
data->transfer_multiplier_multiplier= unpack_int(buf, index);
data->transfer_multiplier_increment = unpack_int(buf, index);
data->transfer_increment_multiplier = unpack_int(buf, index);
data->transfer_increment_increment = unpack_int(buf, index);
}
#endif
 
/*}}}*/
/*{{{ Testcase definitions */
 
// ----------------------------------------------------------------------------
// Definitions of the supported test cases. The actual implementations need
// to vary between host and target.
 
typedef enum usbtest {
usbtest_invalid = 0,
usbtest_bulk_out = 1,
usbtest_bulk_in = 2,
usbtest_control_in = 3
} usbtest;
 
// What I/O mechanism should be used on the target to process data?
typedef enum usb_io_mechanism {
usb_io_mechanism_usb = 1, // The low-level USB-specific API
usb_io_mechanism_dev = 2 // cyg_devio_cread() et al
} usb_io_mechanism;
 
// Bulk transfers. The same structure can be used for IN and OUT transfers.
// The endpoint number will be or'd with either USB_DIR_IN or USB_DIR_OUT,
// or the equivalent under eCos.
typedef struct UsbTest_Bulk {
int number_packets;
int endpoint;
int tx_size;
int tx_size_min;
int tx_size_max;
int tx_size_multiplier;
int tx_size_divisor;
int tx_size_increment;
int rx_size;
int rx_size_min;
int rx_size_max;
int rx_size_multiplier;
int rx_size_divisor;
int rx_size_increment;
int rx_padding;
int tx_delay;
int tx_delay_min;
int tx_delay_max;
int tx_delay_multiplier;
int tx_delay_divisor;
int tx_delay_increment;
int rx_delay;
int rx_delay_min;
int rx_delay_max;
int rx_delay_multiplier;
int rx_delay_divisor;
int rx_delay_increment;
usb_io_mechanism io_mechanism;
UsbTestData data;
} UsbTest_Bulk;
 
#ifdef HOST
static void
pack_usbtest_bulk(UsbTest_Bulk* test, unsigned char* buffer, int* index)
{
pack_int(test->number_packets, buffer, index);
pack_int(test->endpoint, buffer, index);
pack_int(test->tx_size, buffer, index);
pack_int(test->tx_size_min, buffer, index);
pack_int(test->tx_size_max, buffer, index);
pack_int(test->tx_size_multiplier, buffer, index);
pack_int(test->tx_size_divisor, buffer, index);
pack_int(test->tx_size_increment, buffer, index);
pack_int(test->rx_size, buffer, index);
pack_int(test->rx_size_min, buffer, index);
pack_int(test->rx_size_max, buffer, index);
pack_int(test->rx_size_multiplier, buffer, index);
pack_int(test->rx_size_divisor, buffer, index);
pack_int(test->rx_size_increment, buffer, index);
// There is no need to transfer the padding field. It is only of
// interest on the host, and this message is being packed
// for the target side.
pack_int(test->tx_delay, buffer, index);
pack_int(test->tx_delay_min, buffer, index);
pack_int(test->tx_delay_max, buffer, index);
pack_int(test->tx_delay_multiplier, buffer, index);
pack_int(test->tx_delay_divisor, buffer, index);
pack_int(test->tx_delay_increment, buffer, index);
pack_int(test->rx_delay, buffer, index);
pack_int(test->rx_delay_min, buffer, index);
pack_int(test->rx_delay_max, buffer, index);
pack_int(test->rx_delay_multiplier, buffer, index);
pack_int(test->rx_delay_divisor, buffer, index);
pack_int(test->rx_delay_increment, buffer, index);
pack_int((int)test->io_mechanism, buffer, index);
pack_usbtestdata(&(test->data), buffer, index);
}
#endif
 
#ifdef TARGET
static void
unpack_usbtest_bulk(UsbTest_Bulk* test, unsigned char* buffer, int* index)
{
test->number_packets = unpack_int(buffer, index);
test->endpoint = unpack_int(buffer, index);
test->tx_size = unpack_int(buffer, index);
test->tx_size_min = unpack_int(buffer, index);
test->tx_size_max = unpack_int(buffer, index);
test->tx_size_multiplier = unpack_int(buffer, index);
test->tx_size_divisor = unpack_int(buffer, index);
test->tx_size_increment = unpack_int(buffer, index);
test->rx_size = unpack_int(buffer, index);
test->rx_size_min = unpack_int(buffer, index);
test->rx_size_max = unpack_int(buffer, index);
test->rx_size_multiplier = unpack_int(buffer, index);
test->rx_size_divisor = unpack_int(buffer, index);
test->rx_size_increment = unpack_int(buffer, index);
test->tx_delay = unpack_int(buffer, index);
test->tx_delay_min = unpack_int(buffer, index);
test->tx_delay_max = unpack_int(buffer, index);
test->tx_delay_multiplier = unpack_int(buffer, index);
test->tx_delay_divisor = unpack_int(buffer, index);
test->tx_delay_increment = unpack_int(buffer, index);
test->rx_delay = unpack_int(buffer, index);
test->rx_delay_min = unpack_int(buffer, index);
test->rx_delay_max = unpack_int(buffer, index);
test->rx_delay_multiplier = unpack_int(buffer, index);
test->rx_delay_divisor = unpack_int(buffer, index);
test->rx_delay_increment = unpack_int(buffer, index);
test->io_mechanism = (usb_io_mechanism) unpack_int(buffer, index);
unpack_usbtestdata(&(test->data), buffer, index);
}
#endif
 
// A macro for moving on the next packet size. This also has to be shared between host
// and target, if the two got out of synch then testing would go horribly wrong.
//
// The new packet size is determined using a multiplier and increment,
// so to e.g. increase packet sizes by 4 bytes each time the
// multiplier would be 1 and the increment would be 4, or to double
// packet sizes the multiplier would be 2 and the increment would be
// 0. On underflow or overflow the code tries to adjust the packet size
// back to within the accepted range.
 
#define USBTEST_NEXT_TX_SIZE(_x_) \
do { \
_x_.tx_size *= _x_.tx_size_multiplier; \
_x_.tx_size /= _x_.tx_size_divisor; \
_x_.tx_size += _x_.tx_size_increment; \
if (_x_.tx_size < _x_.tx_size_min) { \
if (_x_.tx_size_min == _x_.tx_size_max) { \
_x_.tx_size = _x_.tx_size_min; \
} else { \
int tmp = _x_.tx_size_min - _x_.tx_size; \
tmp %= _x_.tx_size_max - _x_.tx_size_min; \
_x_.tx_size = tmp + _x_.tx_size_min; \
} \
} else if (_x_.tx_size > _x_.tx_size_max) { \
if (_x_.tx_size_min == _x_.tx_size_max) { \
_x_.tx_size = _x_.tx_size_max; \
} else { \
int tmp = _x_.tx_size - _x_.tx_size_max; \
tmp %= _x_.tx_size_max - _x_.tx_size_min; \
_x_.tx_size = tmp + _x_.tx_size_min; \
} \
} \
} while ( 0 )
 
// A similar macro for moving on to the next receive size. This is less
// critical since care is taken to always receive at least the current
// tx size plus padding.
// Note that padding needs to be added by the calling code, not here,
// since padding is only applicable on the host-side and this macro
// is used on both host and target.
#define USBTEST_NEXT_RX_SIZE(_x_) \
do { \
_x_.rx_size *= _x_.rx_size_multiplier; \
_x_.rx_size /= _x_.rx_size_divisor; \
_x_.rx_size += _x_.rx_size_increment; \
if (_x_.rx_size < _x_.rx_size_min) { \
if (_x_.rx_size_min == _x_.rx_size_max) { \
_x_.rx_size = _x_.rx_size_min; \
} else { \
int tmp = _x_.rx_size_min - _x_.rx_size; \
tmp %= _x_.rx_size_max - _x_.rx_size_min; \
_x_.rx_size = tmp + _x_.rx_size_min; \
} \
} else if (_x_.rx_size > _x_.rx_size_max) { \
if (_x_.rx_size_min == _x_.rx_size_max) { \
_x_.rx_size = _x_.rx_size_max; \
} else { \
int tmp = _x_.rx_size - _x_.rx_size_max; \
tmp %= _x_.rx_size_max - _x_.rx_size_min; \
_x_.rx_size = tmp + _x_.rx_size_min; \
} \
} \
} while ( 0 )
 
// And a macro for adjusting the transmit delay.
#define USBTEST_NEXT_TX_DELAY(_x_) \
do { \
_x_.tx_delay *= _x_.tx_delay_multiplier; \
_x_.tx_delay /= _x_.tx_delay_divisor; \
_x_.tx_delay += _x_.tx_delay_increment; \
if (_x_.tx_delay < _x_.tx_delay_min) { \
if (_x_.tx_delay_min == _x_.tx_delay_max) { \
_x_.tx_delay = _x_.tx_delay_min; \
} else { \
int tmp = _x_.tx_delay_min - _x_.tx_delay; \
tmp %= _x_.tx_delay_max - _x_.tx_delay_min; \
_x_.tx_delay = tmp + _x_.tx_delay_min; \
} \
} else if (_x_.tx_delay > _x_.tx_delay_max) { \
if (_x_.tx_delay_min == _x_.tx_delay_max) { \
_x_.tx_delay = _x_.tx_delay_max; \
} else { \
int tmp = _x_.tx_delay - _x_.tx_delay_max; \
tmp %= _x_.tx_delay_max - _x_.tx_delay_min; \
_x_.tx_delay = tmp + _x_.tx_delay_min; \
} \
} \
} while ( 0 )
 
#define USBTEST_NEXT_RX_DELAY(_x_) \
do { \
_x_.rx_delay *= _x_.rx_delay_multiplier; \
_x_.rx_delay /= _x_.rx_delay_divisor; \
_x_.rx_delay += _x_.rx_delay_increment; \
if (_x_.rx_delay < _x_.rx_delay_min) { \
if (_x_.rx_delay_min == _x_.rx_delay_max) { \
_x_.rx_delay = _x_.rx_delay_min; \
} else { \
int tmp = _x_.rx_delay_min - _x_.rx_delay; \
tmp %= _x_.rx_delay_max - _x_.rx_delay_min; \
_x_.rx_delay = tmp + _x_.rx_delay_min; \
} \
} else if (_x_.rx_delay > _x_.rx_delay_max) { \
if (_x_.rx_delay_min == _x_.rx_delay_max) { \
_x_.rx_delay = _x_.rx_delay_max; \
} else { \
int tmp = _x_.rx_delay - _x_.rx_delay_max; \
tmp %= _x_.rx_delay_max - _x_.rx_delay_min; \
_x_.rx_delay = tmp + _x_.rx_delay_min; \
} \
} \
} while ( 0 )
 
#define USBTEST_BULK_NEXT(_bulk_) \
USBTEST_NEXT_TX_SIZE(_bulk_); \
USBTEST_NEXT_RX_SIZE(_bulk_); \
USBTEST_NEXT_TX_DELAY(_bulk_); \
USBTEST_NEXT_RX_DELAY(_bulk_);
 
// Control transfers, receives
typedef struct UsbTest_ControlIn {
int number_packets;
int packet_size_initial;
int packet_size_min;
int packet_size_max;
int packet_size_multiplier;
int packet_size_increment;
UsbTestData data;
} UsbTest_ControlIn;
 
#ifdef HOST
static void
pack_usbtest_control_in(UsbTest_ControlIn* test, unsigned char* buffer, int* index)
{
pack_int(test->number_packets, buffer, index);
pack_int(test->packet_size_initial, buffer, index);
pack_int(test->packet_size_min, buffer, index);
pack_int(test->packet_size_max, buffer, index);
pack_int(test->packet_size_multiplier, buffer, index);
pack_int(test->packet_size_increment, buffer, index);
pack_usbtestdata(&(test->data), buffer, index);
}
#endif
 
#ifdef TARGET
static void
unpack_usbtest_control_in(UsbTest_ControlIn* test, unsigned char* buffer, int* index)
{
test->number_packets = unpack_int(buffer, index);
test->packet_size_initial = unpack_int(buffer, index);
test->packet_size_min = unpack_int(buffer, index);
test->packet_size_max = unpack_int(buffer, index);
test->packet_size_multiplier = unpack_int(buffer, index);
test->packet_size_increment = unpack_int(buffer, index);
unpack_usbtestdata(&(test->data), buffer, index);
}
#endif
 
// For now control packet sizes are adjusted in exactly the same way as bulk transfers.
#define USBTEST_CONTROL_NEXT_PACKET_SIZE(_packet_size_, _control_) \
_packet_size_ = (_packet_size_ * _control_.packet_size_multiplier) + _control_.packet_size_increment; \
if (_packet_size_ < _control_.packet_size_min) { \
_packet_size_ += _control_.packet_size_max - _control_.packet_size_min; \
if (_packet_size_ < _control_.packet_size_min) { \
_packet_size_ = _control_.packet_size_initial; \
} \
} else if (_packet_size_ > _control_.packet_size_max) { \
_packet_size_ -= _control_.packet_size_max - _control_.packet_size_min; \
if (_packet_size_ > _control_.packet_size_max) { \
_packet_size_ = _control_.packet_size_initial; \
} \
}
 
/*}}}*/
/*{{{ Recovery support */
 
// ----------------------------------------------------------------------------
// When things go wrong threads on either the host or the target may get
// locked up waiting for further communication that never happens, because
// the other side has already raised an error. Recovery is possible by
// performing an extra I/O operation. For example, if a thread on the
// target is blocked waiting on an OUT endpoint then recovery is possible
// by the host sending some data to that endpoint. Similarly if a thread
// on the host is blocked then recovery involves the target either sending
// or receiving some additional data. There are alternative approaches such
// as stalling endpoints, but making sure that the requested communication
// actually happens involves fewer dependencies on exactly how those
// operations behave.
 
typedef struct UsbTest_Recovery {
int endpoint; // Top bit indicates direction, -1 indicates invalid
int protocol;
int size;
} UsbTest_Recovery;
 
static void
pack_usbtest_recovery(UsbTest_Recovery* recovery, unsigned char* buffer, int* index)
{
pack_int(recovery->endpoint, buffer, index);
pack_int(recovery->protocol, buffer, index);
pack_int(recovery->size, buffer, index);
}
 
static void
unpack_usbtest_recovery(UsbTest_Recovery* recovery, unsigned char* buffer, int *index)
{
recovery->endpoint = unpack_int(buffer, index);
recovery->protocol = unpack_int(buffer, index);
recovery->size = unpack_int(buffer, index);
}
 
static void
usbtest_recovery_reset(UsbTest_Recovery* recovery)
{
recovery->endpoint = -1;
recovery->protocol = 0;
recovery->size = 0;
}
 
/*}}}*/

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.