// SPDX-License-Identifier: BSD-3-Clause /* * Loopback test application * * Copyright 2015 Google Inc. * Copyright 2015 Linaro Ltd. * * Provided under the three clause BSD license found in the LICENSE file. */ #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_NUM_DEVICES 10 #define MAX_SYSFS_PREFIX 0x80 #define MAX_SYSFS_PATH 0x200 #define CSV_MAX_LINE 0x1000 #define SYSFS_MAX_INT 0x20 #define MAX_STR_LEN 255 #define DEFAULT_ASYNC_TIMEOUT 200000 struct dict { char *name; int type; }; static struct dict dict[] = { {"ping", 2}, {"transfer", 3}, {"sink", 4}, {NULL,} /* list termination */ }; struct loopback_results { float latency_avg; uint32_t latency_max; uint32_t latency_min; uint32_t latency_jitter; float request_avg; uint32_t request_max; uint32_t request_min; uint32_t request_jitter; float throughput_avg; uint32_t throughput_max; uint32_t throughput_min; uint32_t throughput_jitter; float apbridge_unipro_latency_avg; uint32_t apbridge_unipro_latency_max; uint32_t apbridge_unipro_latency_min; uint32_t apbridge_unipro_latency_jitter; float gbphy_firmware_latency_avg; uint32_t gbphy_firmware_latency_max; uint32_t gbphy_firmware_latency_min; uint32_t gbphy_firmware_latency_jitter; uint32_t error; }; struct loopback_device { char name[MAX_STR_LEN]; char sysfs_entry[MAX_SYSFS_PATH]; char debugfs_entry[MAX_SYSFS_PATH]; struct loopback_results results; }; struct loopback_test { int verbose; int debug; int raw_data_dump; int porcelain; int mask; int size; int iteration_max; int aggregate_output; int test_id; int device_count; int list_devices; int use_async; int async_timeout; int async_outstanding_operations; int us_wait; int file_output; int stop_all; int poll_count; char test_name[MAX_STR_LEN]; char sysfs_prefix[MAX_SYSFS_PREFIX]; char debugfs_prefix[MAX_SYSFS_PREFIX]; struct timespec poll_timeout; struct loopback_device devices[MAX_NUM_DEVICES]; struct loopback_results aggregate_results; struct pollfd fds[MAX_NUM_DEVICES]; }; struct loopback_test t; /* Helper macros to calculate the aggregate results for all devices */ static inline int device_enabled(struct loopback_test *t, int dev_idx); #define GET_MAX(field) \ static int get_##field##_aggregate(struct loopback_test *t) \ { \ uint32_t max = 0; \ int i; \ for (i = 0; i < t->device_count; i++) { \ if (!device_enabled(t, i)) \ continue; \ if (t->devices[i].results.field > max) \ max = t->devices[i].results.field; \ } \ return max; \ } \ #define GET_MIN(field) \ static int get_##field##_aggregate(struct loopback_test *t) \ { \ uint32_t min = ~0; \ int i; \ for (i = 0; i < t->device_count; i++) { \ if (!device_enabled(t, i)) \ continue; \ if (t->devices[i].results.field < min) \ min = t->devices[i].results.field; \ } \ return min; \ } \ #define GET_AVG(field) \ static int get_##field##_aggregate(struct loopback_test *t) \ { \ uint32_t val = 0; \ uint32_t count = 0; \ int i; \ for (i = 0; i < t->device_count; i++) { \ if (!device_enabled(t, i)) \ continue; \ count++; \ val += t->devices[i].results.field; \ } \ if (count) \ val /= count; \ return val; \ } \ GET_MAX(throughput_max); GET_MAX(request_max); GET_MAX(latency_max); GET_MAX(apbridge_unipro_latency_max); GET_MAX(gbphy_firmware_latency_max); GET_MIN(throughput_min); GET_MIN(request_min); GET_MIN(latency_min); GET_MIN(apbridge_unipro_latency_min); GET_MIN(gbphy_firmware_latency_min); GET_AVG(throughput_avg); GET_AVG(request_avg); GET_AVG(latency_avg); GET_AVG(apbridge_unipro_latency_avg); GET_AVG(gbphy_firmware_latency_avg); void abort(void) { _exit(1); } void usage(void) { fprintf(stderr, "Usage: loopback_test TEST [SIZE] ITERATIONS [SYSPATH] [DBGPATH]\n\n" " Run TEST for a number of ITERATIONS with operation data SIZE bytes\n" " TEST may be \'ping\' \'transfer\' or \'sink\'\n" " SIZE indicates the size of transfer <= greybus max payload bytes\n" " ITERATIONS indicates the number of times to execute TEST at SIZE bytes\n" " Note if ITERATIONS is set to zero then this utility will\n" " initiate an infinite (non terminating) test and exit\n" " without logging any metrics data\n" " SYSPATH indicates the sysfs path for the loopback greybus entries e.g.\n" " /sys/bus/greybus/devices\n" " DBGPATH indicates the debugfs path for the loopback greybus entries e.g.\n" " /sys/kernel/debug/gb_loopback/\n" " Mandatory arguments\n" " -t must be one of the test names - sink, transfer or ping\n" " -i iteration count - the number of iterations to run the test over\n" " Optional arguments\n" " -S sysfs location - location for greybus 'endo' entires default /sys/bus/greybus/devices/\n" " -D debugfs location - location for loopback debugfs entries default /sys/kernel/debug/gb_loopback/\n" " -s size of data packet to send during test - defaults to zero\n" " -m mask - a bit mask of connections to include example: -m 8 = 4th connection -m 9 = 1st and 4th connection etc\n" " default is zero which means broadcast to all connections\n" " -v verbose output\n" " -d debug output\n" " -r raw data output - when specified the full list of latency values are included in the output CSV\n" " -p porcelain - when specified printout is in a user-friendly non-CSV format. This option suppresses writing to CSV file\n" " -a aggregate - show aggregation of all enabled devices\n" " -l list found loopback devices and exit\n" " -x Async - Enable async transfers\n" " -o Async Timeout - Timeout in uSec for async operations\n" " -O Poll loop time out in seconds(max time a test is expected to last, default: 30sec)\n" " -c Max number of outstanding operations for async operations\n" " -w Wait in uSec between operations\n" " -z Enable output to a CSV file (incompatible with -p)\n" " -f When starting new loopback test, stop currently running tests on all devices\n" "Examples:\n" " Send 10000 transfers with a packet size of 128 bytes to all active connections\n" " loopback_test -t transfer -s 128 -i 10000 -S /sys/bus/greybus/devices/ -D /sys/kernel/debug/gb_loopback/\n" " loopback_test -t transfer -s 128 -i 10000 -m 0\n" " Send 10000 transfers with a packet size of 128 bytes to connection 1 and 4\n" " loopback_test -t transfer -s 128 -i 10000 -m 9\n" " loopback_test -t ping -s 0 128 -i -S /sys/bus/greybus/devices/ -D /sys/kernel/debug/gb_loopback/\n" " loopback_test -t sink -s 2030 -i 32768 -S /sys/bus/greybus/devices/ -D /sys/kernel/debug/gb_loopback/\n"); abort(); } static inline int device_enabled(struct loopback_test *t, int dev_idx) { if (!t->mask || (t->mask & (1 << dev_idx))) return 1; return 0; } static void show_loopback_devices(struct loopback_test *t) { int i; if (t->device_count == 0) { printf("No loopback devices.\n"); return; } for (i = 0; i < t->device_count; i++) printf("device[%d] = %s\n", i, t->devices[i].name); } int open_sysfs(const char *sys_pfx, const char *node, int flags) { int fd; char path[MAX_SYSFS_PATH]; snprintf(path, sizeof(path), "%s%s", sys_pfx, node); fd = open(path, flags); if (fd < 0) { fprintf(stderr, "unable to open %s\n", path); abort(); } return fd; } int read_sysfs_int_fd(int fd, const char *sys_pfx, const char *node) { char buf[SYSFS_MAX_INT]; if (read(fd, buf, sizeof(buf)) < 0) { fprintf(stderr, "unable to read from %s%s %s\n", sys_pfx, node, strerror(errno)); close(fd); abort(); } return atoi(buf); } float read_sysfs_float_fd(int fd, const char *sys_pfx, const char *node) { char buf[SYSFS_MAX_INT]; if (read(fd, buf, sizeof(buf)) < 0) { fprintf(stderr, "unable to read from %s%s %s\n", sys_pfx, node, strerror(errno)); close(fd); abort(); } return atof(buf); } int read_sysfs_int(const char *sys_pfx, const char *node) { int fd, val; fd = open_sysfs(sys_pfx, node, O_RDONLY); val = read_sysfs_int_fd(fd, sys_pfx, node); close(fd); return val; } float read_sysfs_float(const char *sys_pfx, const char *node) { int fd; float val; fd = open_sysfs(sys_pfx, node, O_RDONLY); val = read_sysfs_float_fd(fd, sys_pfx, node); close(fd); return val; } void write_sysfs_val(const char *sys_pfx, const char *node, int val) { int fd, len; char buf[SYSFS_MAX_INT]; fd = open_sysfs(sys_pfx, node, O_RDWR); len = snprintf(buf, sizeof(buf), "%d", val); if (write(fd, buf, len) < 0) { fprintf(stderr, "unable to write to %s%s %s\n", sys_pfx, node, strerror(errno)); close(fd); abort(); } close(fd); } static int get_results(struct loopback_test *t) { struct loopback_device *d; struct loopback_results *r; int i; for (i = 0; i < t->device_count; i++) { if (!device_enabled(t, i)) continue; d = &t->devices[i]; r = &d->results; r->error = read_sysfs_int(d->sysfs_entry, "error"); r->request_min = read_sysfs_int(d->sysfs_entry, "requests_per_second_min"); r->request_max = read_sysfs_int(d->sysfs_entry, "requests_per_second_max"); r->request_avg = read_sysfs_float(d->sysfs_entry, "requests_per_second_avg"); r->latency_min = read_sysfs_int(d->sysfs_entry, "latency_min"); r->latency_max = read_sysfs_int(d->sysfs_entry, "latency_max"); r->latency_avg = read_sysfs_float(d->sysfs_entry, "latency_avg"); r->throughput_min = read_sysfs_int(d->sysfs_entry, "throughput_min"); r->throughput_max = read_sysfs_int(d->sysfs_entry, "throughput_max"); r->throughput_avg = read_sysfs_float(d->sysfs_entry, "throughput_avg"); r->apbridge_unipro_latency_min = read_sysfs_int(d->sysfs_entry, "apbridge_unipro_latency_min"); r->apbridge_unipro_latency_max = read_sysfs_int(d->sysfs_entry, "apbridge_unipro_latency_max"); r->apbridge_unipro_latency_avg = read_sysfs_float(d->sysfs_entry, "apbridge_unipro_latency_avg"); r->gbphy_firmware_latency_min = read_sysfs_int(d->sysfs_entry, "gbphy_firmware_latency_min"); r->gbphy_firmware_latency_max = read_sysfs_int(d->sysfs_entry, "gbphy_firmware_latency_max"); r->gbphy_firmware_latency_avg = read_sysfs_float(d->sysfs_entry, "gbphy_firmware_latency_avg"); r->request_jitter = r->request_max - r->request_min; r->latency_jitter = r->latency_max - r->latency_min; r->throughput_jitter = r->throughput_max - r->throughput_min; r->apbridge_unipro_latency_jitter = r->apbridge_unipro_latency_max - r->apbridge_unipro_latency_min; r->gbphy_firmware_latency_jitter = r->gbphy_firmware_latency_max - r->gbphy_firmware_latency_min; } /*calculate the aggregate results of all enabled devices */ if (t->aggregate_output) { r = &t->aggregate_results; r->request_min = get_request_min_aggregate(t); r->request_max = get_request_max_aggregate(t); r->request_avg = get_request_avg_aggregate(t); r->latency_min = get_latency_min_aggregate(t); r->latency_max = get_latency_max_aggregate(t); r->latency_avg = get_latency_avg_aggregate(t); r->throughput_min = get_throughput_min_aggregate(t); r->throughput_max = get_throughput_max_aggregate(t); r->throughput_avg = get_throughput_avg_aggregate(t); r->apbridge_unipro_latency_min = get_apbridge_unipro_latency_min_aggregate(t); r->apbridge_unipro_latency_max = get_apbridge_unipro_latency_max_aggregate(t); r->apbridge_unipro_latency_avg = get_apbridge_unipro_latency_avg_aggregate(t); r->gbphy_firmware_latency_min = get_gbphy_firmware_latency_min_aggregate(t); r->gbphy_firmware_latency_max = get_gbphy_firmware_latency_max_aggregate(t); r->gbphy_firmware_latency_avg = get_gbphy_firmware_latency_avg_aggregate(t); r->request_jitter = r->request_max - r->request_min; r->latency_jitter = r->latency_max - r->latency_min; r->throughput_jitter = r->throughput_max - r->throughput_min; r->apbridge_unipro_latency_jitter = r->apbridge_unipro_latency_max - r->apbridge_unipro_latency_min; r->gbphy_firmware_latency_jitter = r->gbphy_firmware_latency_max - r->gbphy_firmware_latency_min; } return 0; } void log_csv_error(int len, int err) { fprintf(stderr, "unable to write %d bytes to csv %s\n", len, strerror(err)); } int format_output(struct loopback_test *t, struct loopback_results *r, const char *dev_name, char *buf, int buf_len, struct tm *tm) { int len = 0; memset(buf, 0x00, buf_len); len = snprintf(buf, buf_len, "%u-%u-%u %u:%u:%u", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); if (t->porcelain) { len += snprintf(&buf[len], buf_len - len, "\n test:\t\t\t%s\n path:\t\t\t%s\n size:\t\t\t%u\n iterations:\t\t%u\n errors:\t\t%u\n async:\t\t\t%s\n", t->test_name, dev_name, t->size, t->iteration_max, r->error, t->use_async ? "Enabled" : "Disabled"); len += snprintf(&buf[len], buf_len - len, " requests per-sec:\tmin=%u, max=%u, average=%f, jitter=%u\n", r->request_min, r->request_max, r->request_avg, r->request_jitter); len += snprintf(&buf[len], buf_len - len, " ap-throughput B/s:\tmin=%u max=%u average=%f jitter=%u\n", r->throughput_min, r->throughput_max, r->throughput_avg, r->throughput_jitter); len += snprintf(&buf[len], buf_len - len, " ap-latency usec:\tmin=%u max=%u average=%f jitter=%u\n", r->latency_min, r->latency_max, r->latency_avg, r->latency_jitter); len += snprintf(&buf[len], buf_len - len, " apbridge-latency usec:\tmin=%u max=%u average=%f jitter=%u\n", r->apbridge_unipro_latency_min, r->apbridge_unipro_latency_max, r->apbridge_unipro_latency_avg, r->apbridge_unipro_latency_jitter); len += snprintf(&buf[len], buf_len - len, " gbphy-latency usec:\tmin=%u max=%u average=%f jitter=%u\n", r->gbphy_firmware_latency_min, r->gbphy_firmware_latency_max, r->gbphy_firmware_latency_avg, r->gbphy_firmware_latency_jitter); } else { len += snprintf(&buf[len], buf_len - len, ",%s,%s,%u,%u,%u", t->test_name, dev_name, t->size, t->iteration_max, r->error); len += snprintf(&buf[len], buf_len - len, ",%u,%u,%f,%u", r->request_min, r->request_max, r->request_avg, r->request_jitter); len += snprintf(&buf[len], buf_len - len, ",%u,%u,%f,%u", r->latency_min, r->latency_max, r->latency_avg, r->latency_jitter); len += snprintf(&buf[len], buf_len - len, ",%u,%u,%f,%u", r->throughput_min, r->throughput_max, r->throughput_avg, r->throughput_jitter); len += snprintf(&buf[len], buf_len - len, ",%u,%u,%f,%u", r->apbridge_unipro_latency_min, r->apbridge_unipro_latency_max, r->apbridge_unipro_latency_avg, r->apbridge_unipro_latency_jitter); len += snprintf(&buf[len], buf_len - len, ",%u,%u,%f,%u", r->gbphy_firmware_latency_min, r->gbphy_firmware_latency_max, r->gbphy_firmware_latency_avg, r->gbphy_firmware_latency_jitter); } printf("\n%s\n", buf); return len; } static int log_results(struct loopback_test *t) { int fd, i, len, ret; struct tm tm; time_t local_time; char file_name[MAX_SYSFS_PATH]; char data[CSV_MAX_LINE]; local_time = time(NULL); tm = *localtime(&local_time); /* * file name will test_name_size_iteration_max.csv * every time the same test with the same parameters is run we will then * append to the same CSV with datestamp - representing each test * dataset. */ if (t->file_output && !t->porcelain) { snprintf(file_name, sizeof(file_name), "%s_%d_%d.csv", t->test_name, t->size, t->iteration_max); fd = open(file_name, O_WRONLY | O_CREAT | O_APPEND, 0644); if (fd < 0) { fprintf(stderr, "unable to open %s for appendation\n", file_name); abort(); } } for (i = 0; i < t->device_count; i++) { if (!device_enabled(t, i)) continue; len = format_output(t, &t->devices[i].results, t->devices[i].name, data, sizeof(data), &tm); if (t->file_output && !t->porcelain) { ret = write(fd, data, len); if (ret == -1) fprintf(stderr, "unable to write %d bytes to csv.\n", len); } } if (t->aggregate_output) { len = format_output(t, &t->aggregate_results, "aggregate", data, sizeof(data), &tm); if (t->file_output && !t->porcelain) { ret = write(fd, data, len); if (ret == -1) fprintf(stderr, "unable to write %d bytes to csv.\n", len); } } if (t->file_output && !t->porcelain) close(fd); return 0; } int is_loopback_device(const char *path, const char *node) { char file[MAX_SYSFS_PATH]; snprintf(file, MAX_SYSFS_PATH, "%s%s/iteration_count", path, node); if (access(file, F_OK) == 0) return 1; return 0; } int find_loopback_devices(struct loopback_test *t) { struct dirent **namelist; int i, n, ret; unsigned int dev_id; struct loopback_device *d; n = scandir(t->sysfs_prefix, &namelist, NULL, alphasort); if (n < 0) { perror("scandir"); ret = -ENODEV; goto baddir; } /* Don't include '.' and '..' */ if (n <= 2) { ret = -ENOMEM; goto done; } for (i = 0; i < n; i++) { ret = sscanf(namelist[i]->d_name, "gb_loopback%u", &dev_id); if (ret != 1) continue; if (!is_loopback_device(t->sysfs_prefix, namelist[i]->d_name)) continue; if (t->device_count == MAX_NUM_DEVICES) { fprintf(stderr, "max number of devices reached!\n"); break; } d = &t->devices[t->device_count++]; snprintf(d->name, MAX_STR_LEN, "gb_loopback%u", dev_id); snprintf(d->sysfs_entry, MAX_SYSFS_PATH, "%s%s/", t->sysfs_prefix, d->name); snprintf(d->debugfs_entry, MAX_SYSFS_PATH, "%sraw_latency_%s", t->debugfs_prefix, d->name); if (t->debug) printf("add %s %s\n", d->sysfs_entry, d->debugfs_entry); } ret = 0; done: for (i = 0; i < n; i++) free(namelist[i]); free(namelist); baddir: return ret; } static int open_poll_files(struct loopback_test *t) { struct loopback_device *dev; char buf[MAX_SYSFS_PATH + MAX_STR_LEN]; char dummy; int fds_idx = 0; int i; for (i = 0; i < t->device_count; i++) { dev = &t->devices[i]; if (!device_enabled(t, i)) continue; snprintf(buf, sizeof(buf), "%s%s", dev->sysfs_entry, "iteration_count"); t->fds[fds_idx].fd = open(buf, O_RDONLY); if (t->fds[fds_idx].fd < 0) { fprintf(stderr, "Error opening poll file!\n"); goto err; } read(t->fds[fds_idx].fd, &dummy, 1); t->fds[fds_idx].events = POLLERR | POLLPRI; t->fds[fds_idx].revents = 0; fds_idx++; } t->poll_count = fds_idx; return 0; err: for (i = 0; i < fds_idx; i++) close(t->fds[i].fd); return -1; } static int close_poll_files(struct loopback_test *t) { int i; for (i = 0; i < t->poll_count; i++) close(t->fds[i].fd); return 0; } static int is_complete(struct loopback_test *t) { int iteration_count; int i; for (i = 0; i < t->device_count; i++) { if (!device_enabled(t, i)) continue; iteration_count = read_sysfs_int(t->devices[i].sysfs_entry, "iteration_count"); /* at least one device did not finish yet */ if (iteration_count != t->iteration_max) return 0; } return 1; } static void stop_tests(struct loopback_test *t) { int i; for (i = 0; i < t->device_count; i++) { if (!device_enabled(t, i)) continue; write_sysfs_val(t->devices[i].sysfs_entry, "type", 0); } } static void handler(int sig) { /* do nothing */ } static int wait_for_complete(struct loopback_test *t) { int number_of_events = 0; char dummy; int ret; int i; struct timespec *ts = NULL; struct sigaction sa; sigset_t mask_old, mask; sigemptyset(&mask); sigemptyset(&mask_old); sigaddset(&mask, SIGINT); sigprocmask(SIG_BLOCK, &mask, &mask_old); sa.sa_handler = handler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); if (sigaction(SIGINT, &sa, NULL) == -1) { fprintf(stderr, "sigaction error\n"); return -1; } if (t->poll_timeout.tv_sec != 0) ts = &t->poll_timeout; while (1) { ret = ppoll(t->fds, t->poll_count, ts, &mask_old); if (ret <= 0) { stop_tests(t); fprintf(stderr, "Poll exit with errno %d\n", errno); return -1; } for (i = 0; i < t->poll_count; i++) { if (t->fds[i].revents & POLLPRI) { /* Dummy read to clear the event */ read(t->fds[i].fd, &dummy, 1); number_of_events++; } } if (number_of_events == t->poll_count) break; } if (!is_complete(t)) { fprintf(stderr, "Iteration count did not finish!\n"); return -1; } return 0; } static void prepare_devices(struct loopback_test *t) { int i; /* * Cancel any running tests on enabled devices. If * stop_all option is given, stop test on all devices. */ for (i = 0; i < t->device_count; i++) if (t->stop_all || device_enabled(t, i)) write_sysfs_val(t->devices[i].sysfs_entry, "type", 0); for (i = 0; i < t->device_count; i++) { if (!device_enabled(t, i)) continue; write_sysfs_val(t->devices[i].sysfs_entry, "us_wait", t->us_wait); /* Set operation size */ write_sysfs_val(t->devices[i].sysfs_entry, "size", t->size); /* Set iterations */ write_sysfs_val(t->devices[i].sysfs_entry, "iteration_max", t->iteration_max); if (t->use_async) { write_sysfs_val(t->devices[i].sysfs_entry, "async", 1); write_sysfs_val(t->devices[i].sysfs_entry, "timeout", t->async_timeout); write_sysfs_val(t->devices[i].sysfs_entry, "outstanding_operations_max", t->async_outstanding_operations); } else write_sysfs_val(t->devices[i].sysfs_entry, "async", 0); } } static int start(struct loopback_test *t) { int i; /* the test starts by writing test_id to the type file. */ for (i = 0; i < t->device_count; i++) { if (!device_enabled(t, i)) continue; write_sysfs_val(t->devices[i].sysfs_entry, "type", t->test_id); } return 0; } void loopback_run(struct loopback_test *t) { int i; int ret; for (i = 0; dict[i].name != NULL; i++) { if (strstr(dict[i].name, t->test_name)) t->test_id = dict[i].type; } if (!t->test_id) { fprintf(stderr, "invalid test %s\n", t->test_name); usage(); return; } prepare_devices(t); ret = open_poll_files(t); if (ret) goto err; start(t); ret = wait_for_complete(t); close_poll_files(t); if (ret) goto err; get_results(t); log_results(t); return; err: printf("Error running test\n"); return; } static int sanity_check(struct loopback_test *t) { int i; if (t->device_count == 0) { fprintf(stderr, "No loopback devices found\n"); return -1; } for (i = 0; i < MAX_NUM_DEVICES; i++) { if (!device_enabled(t, i)) continue; if (t->mask && !strcmp(t->devices[i].name, "")) { fprintf(stderr, "Bad device mask %x\n", (1 << i)); return -1; } } return 0; } int main(int argc, char *argv[]) { int o, ret; char *sysfs_prefix = "/sys/class/gb_loopback/"; char *debugfs_prefix = "/sys/kernel/debug/gb_loopback/"; memset(&t, 0, sizeof(t)); while ((o = getopt(argc, argv, "t:s:i:S:D:m:v::d::r::p::a::l::x::o:O:c:w:z::f::")) != -1) { switch (o) { case 't': snprintf(t.test_name, MAX_STR_LEN, "%s", optarg); break; case 's': t.size = atoi(optarg); break; case 'i': t.iteration_max = atoi(optarg); break; case 'S': snprintf(t.sysfs_prefix, MAX_SYSFS_PREFIX, "%s", optarg); break; case 'D': snprintf(t.debugfs_prefix, MAX_SYSFS_PREFIX, "%s", optarg); break; case 'm': t.mask = atol(optarg); break; case 'v': t.verbose = 1; break; case 'd': t.debug = 1; break; case 'r': t.raw_data_dump = 1; break; case 'p': t.porcelain = 1; break; case 'a': t.aggregate_output = 1; break; case 'l': t.list_devices = 1; break; case 'x': t.use_async = 1; break; case 'o': t.async_timeout = atoi(optarg); break; case 'O': t.poll_timeout.tv_sec = atoi(optarg); break; case 'c': t.async_outstanding_operations = atoi(optarg); break; case 'w': t.us_wait = atoi(optarg); break; case 'z': t.file_output = 1; break; case 'f': t.stop_all = 1; break; default: usage(); return -EINVAL; } } if (!strcmp(t.sysfs_prefix, "")) snprintf(t.sysfs_prefix, MAX_SYSFS_PREFIX, "%s", sysfs_prefix); if (!strcmp(t.debugfs_prefix, "")) snprintf(t.debugfs_prefix, MAX_SYSFS_PREFIX, "%s", debugfs_prefix); ret = find_loopback_devices(&t); if (ret) return ret; ret = sanity_check(&t); if (ret) return ret; if (t.list_devices) { show_loopback_devices(&t); return 0; } if (t.test_name[0] == '\0' || t.iteration_max == 0) usage(); if (t.async_timeout == 0) t.async_timeout = DEFAULT_ASYNC_TIMEOUT; loopback_run(&t); return 0; }