| // SPDX-License-Identifier: BSD-3-Clause |
| /* |
| * Loopback test application |
| * |
| * Copyright 2015 Google Inc. |
| * Copyright 2015 Linaro Ltd. |
| */ |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <poll.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <dirent.h> |
| #include <signal.h> |
| |
| #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' entries 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; |
| } |
| |
| 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; |
| } |