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; |
} |
|
/*}}}*/ |