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