📄 usbhost.c
字号:
// code does is allocate a thread and fill in the appropriate data,
// plus request the target-side to do the same thing.
static int
tcl_test_control_in(ClientData clientData __attribute__ ((unused)),
Tcl_Interp* interp,
int argc,
char** argv)
{
int i;
int tmp;
UsbTest* test;
unsigned char request[USBTEST_MAX_CONTROL_DATA];
int request_index;
// The data consists of 6 numbers for UsbTest_ControlIn itself, and
// another 10 numbers for the test data definition.
if (17 != argc) {
Tcl_SetResult(interp, "wrong # args: should be \"usbtest::_test_control_in <message>\"", TCL_STATIC);
return TCL_ERROR;
}
for (i = 1; i < 17; i++) {
int discard;
if (TCL_OK != Tcl_GetInt(interp, argv[i], &discard)) {
Tcl_SetResult(interp, "invalid argument: all arguments should be numbers", TCL_STATIC);
return TCL_ERROR;
}
}
test = pool_allocate();
test->which_test = usbtest_control_in;
Tcl_GetInt(interp, argv[1], &(test->test_params.control_in.number_packets));
Tcl_GetInt(interp, argv[2], &(test->test_params.control_in.packet_size_initial));
Tcl_GetInt(interp, argv[3], &(test->test_params.control_in.packet_size_min));
Tcl_GetInt(interp, argv[4], &(test->test_params.control_in.packet_size_max));
Tcl_GetInt(interp, argv[5], &(test->test_params.control_in.packet_size_multiplier));
Tcl_GetInt(interp, argv[6], &(test->test_params.control_in.packet_size_increment));
Tcl_GetInt(interp, argv[7], &tmp);
test->test_params.bulk.data.format = (usbtestdata) tmp;
Tcl_GetInt(interp, argv[ 8], &(test->test_params.control_in.data.seed));
Tcl_GetInt(interp, argv[ 9], &(test->test_params.control_in.data.multiplier));
Tcl_GetInt(interp, argv[10], &(test->test_params.control_in.data.increment));
Tcl_GetInt(interp, argv[11], &(test->test_params.control_in.data.transfer_seed_multiplier));
Tcl_GetInt(interp, argv[12], &(test->test_params.control_in.data.transfer_seed_increment));
Tcl_GetInt(interp, argv[13], &(test->test_params.control_in.data.transfer_multiplier_multiplier));
Tcl_GetInt(interp, argv[14], &(test->test_params.control_in.data.transfer_multiplier_increment));
Tcl_GetInt(interp, argv[15], &(test->test_params.control_in.data.transfer_increment_multiplier));
Tcl_GetInt(interp, argv[16], &(test->test_params.control_in.data.transfer_increment_increment));
// That is all the data converted from Tcl to C, and a local thread is set up to handle this
// request. Also set up a thread on the target.
request_index = 0;
pack_usbtest_control_in(&(test->test_params.control_in), request, &request_index);
usb_reliable_control_message(usb_master_fd, USB_TYPE_CLASS | USB_RECIP_DEVICE, USBTEST_TEST_CONTROL_IN, 0, 0,
request_index, request);
remote_thread_count++;
return TCL_OK;
}
/*}}}*/
/*{{{ Cancel the current batch of tests */
static int
tcl_cancel(ClientData clientData __attribute__ ((unused)),
Tcl_Interp* interp,
int argc,
char** argv __attribute__ ((unused)) )
{
if (1 != argc) {
Tcl_SetResult(interp, "wrong # args: should be \"usbtest::cancel\"", TCL_STATIC);
return TCL_ERROR;
}
// Send the request on to the target.
usb_reliable_control_message(usb_master_fd, USB_TYPE_CLASS | USB_RECIP_DEVICE, USBTEST_CANCEL, 0, 0, 0, (void*)0);
// Now cancel all the local tests. This can be done by resetting the counter
// of allocated threads: no actual work will have been started yet.
local_thread_count = 0;
// And synchronise with the target
if (!usb_sync(usb_master_fd, 30)) {
fprintf(stderr, "usbhost: error, target has failed to process test cancel request.\n");
exit(EXIT_FAILURE);
}
remote_thread_count = 0;
return TCL_OK;
}
/*}}}*/
/*{{{ Run a batch of tests */
// ----------------------------------------------------------------------------
// This code does an awful lot of the hard work. Start with various utilities.
// Has the current batch finished as far as the local threads are concerned?
static int
local_batch_finished(void)
{
int result = 1;
int i;
for (i = 0; i < local_thread_count; i++) {
if (pool[i].running) {
result = 0;
break;
}
}
return result;
}
// Has the current batch finished as far as remote threads are concerned?
static int
remote_batch_finished(void)
{
char buf[1];
usb_reliable_control_message(usb_master_fd, USB_TYPE_CLASS | USB_RECIP_DEVICE | USB_DIR_IN, USBTEST_FINISHED,
0, 0, 1, (void*) buf);
return buf[0];
}
// Perform recovery for a thread on the target. This involves asking the
// target for recovery information, then performing an appropriate
// action. If no data is returned then no recovery is needed for this thread.
static void
recover_remote(int index)
{
unsigned char buffer[USBTEST_MAX_CONTROL_DATA];
int buffer_index;
UsbTest_Recovery recovery;
int i;
if (0 != usb_reliable_control_message(usb_master_fd, USB_TYPE_CLASS | USB_RECIP_DEVICE | USB_DIR_IN,
USBTEST_GET_RECOVERY, 0, index, 12, buffer)) {
// There is work to be done
buffer_index = 0;
unpack_usbtest_recovery(&recovery, buffer, &buffer_index);
// We have an endpoint, a protocol, and a size.
if (0 == recovery.endpoint) {
// The target just needs a dummy reserved control message
usb_reliable_control_message(usb_master_fd, USB_TYPE_RESERVED | USB_RECIP_DEVICE, USBTEST_RESERVED_CONTROL_IN,
0, 0, 0, (void*) 0);
} else if (USB_ENDPOINT_XFER_BULK == recovery.protocol) {
// Either we need to send some data to the target, or we need to accept some data.
static unsigned char recovery_buffer[USBTEST_MAX_BULK_DATA + USBTEST_MAX_BULK_DATA_EXTRA];
struct usbdevfs_bulktransfer transfer;
transfer.ep = recovery.endpoint;
transfer.timeout = 2000; // Two seconds. Should be plenty, even for a large bulk transfer.
transfer.data = recovery_buffer;
if (USB_DIR_IN == (recovery.endpoint & USB_ENDPOINT_DIR_MASK)) {
transfer.len = recovery.size;
} else {
transfer.len = 1;
}
errno = 0;
i = ioctl(usb_master_fd, USBDEVFS_BULK, &transfer);
}
// There is no recovery support yet for other protocols.
}
}
// Perform recovery for a local thread. This involves extracting the
// recovery information from the local thread and asking the target
// to take appropriate action.
static void
recover_local(int index)
{
unsigned char buffer[USBTEST_MAX_CONTROL_DATA];
int buffer_index;
if (pool[index].running) {
buffer_index = 0;
pack_usbtest_recovery(&(pool[index].test.recovery), buffer, &buffer_index);
usb_reliable_control_message(usb_master_fd, USB_TYPE_CLASS | USB_RECIP_DEVICE, USBTEST_PERFORM_RECOVERY,
0, 0, buffer_index, (void*) buffer);
}
}
// All done, time for a clean-up on both target and host. The latter
// is achieved simply by resetting the thread pool, which actually
// just means resetting the counter since all the threads are blocked
// waiting for the next batch.
static void
run_done(void)
{
usb_reliable_control_message(usb_master_fd, USB_TYPE_CLASS | USB_RECIP_DEVICE, USBTEST_BATCH_DONE, 0, 0, 0, (void*) NULL);
local_thread_count = 0;
remote_thread_count = 0;
}
// The main routine, as invoked from Tcl. This takes a single
// argument, a timeout in seconds.
static int
tcl_run(ClientData clientData __attribute__ ((unused)),
Tcl_Interp* interp,
int argc,
char** argv __attribute__ ((unused)) )
{
struct timespec delay;
int timeout;
time_t start;
time_t now;
int i, j;
unsigned char result_buf[USBTEST_MAX_CONTROL_DATA];
int all_ok;
if (2 != argc) {
Tcl_SetResult(interp, "wrong # args: should be \"usbtest::_run <timeout>\"", TCL_STATIC);
return TCL_ERROR;
}
if (TCL_OK != Tcl_GetInt(interp, argv[1], &timeout)) {
Tcl_SetResult(interp, "invalid argument: timeout should be numeric", TCL_STATIC);
return TCL_ERROR;
}
VERBOSE(2, "Starting a testrun, timeout %d seconds\n", timeout);
// Start the tests running on the target. The target USB hardware
// will not actually do anything except in response to packets
// from the host, so it is better to start the target before the
// local threads.
usb_reliable_control_message(usb_master_fd, USB_TYPE_CLASS | USB_RECIP_DEVICE, USBTEST_START, 0, 0, 0, (void*) 0);
// Now the local threads can get going.
current_tests_terminated = 0;
pool_start();
// Now leave the various testing threads to do their thing until
// either side believes that the batch has finished, or until the
// timeout expires. Note that if one side decides that the batch
// has finished but the other disagrees, that in itself indicates
// a test failure of sorts.
//
// There is a question of polling frequency. Once a second avoids
// excessive polling traffic on the USB bus, and should not impose
// intolerable delays for short-duration tests.
start = time(NULL);
do {
VERBOSE(3, "The tests are running, waiting for termination\n");
delay.tv_sec = 1;
delay.tv_nsec = 0;
nanosleep(&delay, NULL);
now = time(NULL);
} while (((start + timeout) > now) && !local_batch_finished() && !remote_batch_finished());
VERBOSE(2, "Termination detected, time elapsed %ld\n", (long) now - start);
// If either side believes that testing is not complete, things
// get messy. Start by setting the terminated flag. Any tests that
// are actually still running happily but have not finished within
// the timeout should detect this and stop.
if (!local_batch_finished() || !remote_batch_finished()) {
VERBOSE(2, "Testing is not yet complete, setting TERMINATED flag\n");
current_tests_terminated = 1;
usb_reliable_control_message(usb_master_fd, USB_TYPE_CLASS | USB_RECIP_DEVICE, USBTEST_SET_TERMINATED, 0, 0, 0, (void*) 0);
// And another delay, to give threads a chance to detect the
// flag's update
delay.tv_sec = 1;
delay.tv_nsec = 0;
nanosleep(&delay, NULL);
}
// If there is still are unfinished threads, recovery action
// is needed. It is not clear whether it is better to unlock
// the local threads first, or the remote threads. For now the
// latter approach is taken.
if (!remote_batch_finished()) {
int i;
VERBOSE(2, "Remote threads still running, performing remote recovery\n");
for (i = 0; i < remote_thread_count; i++) {
recover_remote(i);
}
// Allow the recovery actions to take effect
delay.tv_sec = 1;
delay.tv_nsec = 0;
nanosleep(&delay, NULL);
}
if (!local_batch_finished()) {
int i;
VERBOSE(2, "Local threads still running, performing local recovery\n");
for (i = 0; i < local_thread_count; i++) {
recover_local(i);
}
// Allow the recovery actions to take effect
delay.tv_sec = 1;
delay.tv_nsec = 0;
nanosleep(&delay, NULL);
}
// One last check to make sure that everything is finished. If not,
// testing has broken down and it is necessary to abort.
if (!local_batch_finished() || !remote_batch_finished()) {
VERBOSE(2, "Giving local and remote threads another chance to finish.\n");
// Allow the recovery actions to take effect
delay.tv_sec = 5;
delay.tv_nsec = 0;
nanosleep(&delay, NULL);
if (!local_batch_finished() || !remote_batch_finished()) {
// OK, normality has not been restored.
// It would be nice to get hold of and display any error messages.
usb_abort(usb_master_fd);
fprintf(stderr, "Fatal error: the host test program and the remote target are out of synch.\n");
fprintf(stderr, " recovery has been attempted, without success.\n
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -