This a tool to benchmark the latency of GPIO driver, it's able to run 2 kinds of benchmark test:
1, loopback mode 1) apply 2 gpio pins by calling service in gpio RTDM driver like gpio-bcm2835 and gpio-core.c, one is as output, the other is as interrupt 2) call write_rt to send a pulse from output 3) call read_rt to get timestamps recorded in driver (inner loop) 4) also record timespace in user space(outer_loop) outer_loop is inner_loop plus overhead of event wakeup 5) ftrace enable/disable 2, react mode 1) apply 2 gpio pins by calling service in gpio RTDM driver like gpio-bcm2835 and gpio-core.c, one is as ourput, the other is as interrupt 2) call read_rt to wait for a pulse from latency box 3) call write_rt to send a signal back to latency box as a reaction 4) latency box calculates the diff and makes the histogram e.g.: 1) react mode: gpiobench -o 20 -i 21 -c pinctrl-bcm2835 -m 1 -l 1000 2) loopback mode: gpiobench -o 20 -i 21 -c pinctrl-bcm2835 -m 0 -l 1000 -h 100 -b 50 CC: Jan Kiszka <[email protected]> CC: Greg Gallagher <[email protected]> Signed-off-by: chensong <[email protected]> --- configure.ac | 1 + doc/asciidoc/man1/gpiobench.adoc | 70 +++++ testsuite/Makefile.am | 3 +- testsuite/gpiobench/Makefile.am | 18 ++ testsuite/gpiobench/gpiobench.c | 654 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 745 insertions(+), 1 deletion(-) create mode 100644 doc/asciidoc/man1/gpiobench.adoc create mode 100644 testsuite/gpiobench/Makefile.am create mode 100644 testsuite/gpiobench/gpiobench.c diff --git a/configure.ac b/configure.ac index 29fefab..164c449 100644 --- a/configure.ac +++ b/configure.ac @@ -939,6 +939,7 @@ AC_CONFIG_FILES([ \ testsuite/latency/Makefile \ testsuite/switchtest/Makefile \ testsuite/gpiotest/Makefile \ + testsuite/gpiobench/Makefile \ testsuite/spitest/Makefile \ testsuite/smokey/Makefile \ testsuite/smokey/arith/Makefile \ diff --git a/doc/asciidoc/man1/gpiobench.adoc b/doc/asciidoc/man1/gpiobench.adoc new file mode 100644 index 0000000..bd32ea8 --- /dev/null +++ b/doc/asciidoc/man1/gpiobench.adoc @@ -0,0 +1,70 @@ +// ** The above line should force tbl to be a preprocessor ** +// Man page for gpiobench +// +// Copyright (C) 2020 song chen <[email protected]> +// +// You may distribute under the terms of the GNU General Public +// License as specified in the file COPYING that comes with the +// Xenomai distribution. +// +// +GPIOBENCH(1) +========== +:doctype: manpage +:revdate: 2020/08/03 +:man source: Xenomai +:man version: {xenover} +:man manual: Xenomai Manual + +NAME +----- +gpiobench - Xenomai gpio latency benchmark + +SYNOPSIS +--------- +// The general command line +*gpiobench* [ options ] + +DESCRIPTION +------------ +*gpiobench* is part of the Xenomai test suite. It is a gpio latency +benchmark program. The system must run a suitable Xenomai enabled kernel with +the respective module (xeno_timerbench). + +OPTIONS +-------- +*gpiobench* accepts the following options: + +*-h <histogram-size>*:: +default = 100, increase if your last bucket is full + +*-l <num-of-loops>*:: +default=1000, number of loops to run the test + +*-q <quiet>*:: +print only a summary on exit + +*-m <test-mode>*:: +0 = loopback (default), 1 = react + +*-c <pin-controller>*:: +name of pin controller + +*-o <output-pin>*:: +number of gpio pin as output + +*-i <interrupt-pin>*:: +number of gpin pin as interrupt + +*-p <priority>*:: +default = 99, task priority + +*-b <bracktrace>*:: +default = 1000, send break trace command when latency > breaktrace + + + +AUTHOR +------- +*gpiobench* was written by song chen. This man page +was written by song chen. diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am index 76d108e..4932f6d 100644 --- a/testsuite/Makefile.am +++ b/testsuite/Makefile.am @@ -1,5 +1,5 @@ -SUBDIRS = latency smokey +SUBDIRS = latency smokey gpiobench if XENO_COBALT SUBDIRS += \ @@ -13,6 +13,7 @@ endif DIST_SUBDIRS = \ clocktest \ gpiotest \ + gpiobench \ latency \ smokey \ spitest \ diff --git a/testsuite/gpiobench/Makefile.am b/testsuite/gpiobench/Makefile.am new file mode 100644 index 0000000..cca3395 --- /dev/null +++ b/testsuite/gpiobench/Makefile.am @@ -0,0 +1,18 @@ +testdir = @XENO_TEST_DIR@ + +CCLD = $(top_srcdir)/scripts/wrap-link.sh $(CC) + +test_PROGRAMS = gpiobench + +gpiobench_SOURCES = gpiobench.c + +gpiobench_CPPFLAGS = \ + $(XENO_USER_CFLAGS) \ + -I$(top_srcdir)/include + +gpiobench_LDFLAGS = @XENO_AUTOINIT_LDFLAGS@ $(XENO_POSIX_WRAPPERS) + +gpiobench_LDADD = \ + @XENO_CORE_LDADD@ \ + @XENO_USER_LDADD@ \ + -lpthread -lrt -lm diff --git a/testsuite/gpiobench/gpiobench.c b/testsuite/gpiobench/gpiobench.c new file mode 100644 index 0000000..7f19837 --- /dev/null +++ b/testsuite/gpiobench/gpiobench.c @@ -0,0 +1,654 @@ +/* + * Copyright (C) 2020 Song Chen <[email protected]> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include <stdlib.h> +#include <math.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <error.h> +#include <signal.h> +#include <sched.h> +#include <time.h> +#include <sys/time.h> +#include <unistd.h> +#include <pthread.h> +#include <semaphore.h> +#include <sys/timerfd.h> +#include <xeno_config.h> +#include <rtdm/testing.h> +#include <rtdm/gpio.h> +#include <boilerplate/trace.h> +#include <xenomai/init.h> +#include <sys/mman.h> +#include <getopt.h> + +#define NS_PER_MS (1000000) +#define NS_PER_S (1000000000) + +#define DEFAULT_PRIO 99 +#define VERSION_STRING "0.1" +#define GPIO_HIGH 1 +#define GPIO_LOW 0 +#define MAX_HIST 100 +#define MAX_CYCLES 1000000 +#define DEFAULT_LIMIT 1000 +#define DEV_PATH "/dev/rtdm/" +#define TRACING_ON "/sys/kernel/debug/tracing/tracing_on" +#define TRACING_EVENTS "/sys/kernel/debug/tracing/events/enable" +#define TRACE_MARKER "/sys/kernel/debug/tracing/trace_marker" +#define ON "1" +#define OFF "0" + +enum { + MODE_LOOPBACK, + MODE_REACT, + MODE_ALL +}; + +/* Struct for statistics */ +struct test_stat { + long inner_min; + long inner_max; + double inner_avg; + long *inner_hist_array; + long inner_hist_overflow; + long outer_min; + long outer_max; + double outer_avg; + long *outer_hist_array; + long outer_hist_overflow; +}; + +/* Struct for information */ +struct test_info { + unsigned long max_cycles; + unsigned long total_cycles; + unsigned long max_histogram; + int mode; + int prio; + int quiet; + int tracelimit; + int fd_dev_intr; + int fd_dev_out; + char pin_controller[32]; + pthread_t gpio_task; + int gpio_intr; + int gpio_out; + struct test_stat ts; +}; + +struct test_info ti; +/* Print usage information */ +static void display_help(void) +{ + printf("gpiobench V %s\n", VERSION_STRING); + printf("Usage:\n" + "gpiobench <options>\n\n" + + "-b --breaktrace=USEC send break trace command when latency > USEC\n" + " default=1000\n" + "-h --histogram=US dump a latency histogram to stdout after the run\n" + " US is the max time to be tracked in microseconds,\n" + " default=100\n" + "-l --loops number of loops, default=1000\n" + "-p --prio priority of highest prio thread, defaults=99\n" + "-q --quiet print only a summary on exit\n" + "-o --output gpio port number for output, no default value,\n" + " must be specified\n" + "-i --intr gpio port number as an interrupt, no default value,\n" + " must be specified\n" + "-c --pinctrl gpio pin controller's name, no default value,\n" + " must be specified\n" + "-m --testmode 0 is loopback mode\n" + " 1 is react mode which works with a latency box,\n" + " default=0\n\n" + + "e.g. gpiobench -o 20 -i 21 -c pinctrl-bcm2835\n" + ); +} + +static void process_options(int argc, char *argv[]) +{ + int c = 0; + static const char optstring[] = "h:p:m:l:c:b:i:o:q"; + + struct option long_options[] = { + { "bracetrace", required_argument, 0, 'b'}, + { "histogram", required_argument, 0, 'h'}, + { "loops", required_argument, 0, 'l'}, + { "prio", required_argument, 0, 'p'}, + { "quiet", no_argument, 0, 'q'}, + { "output", required_argument, 0, 'o'}, + { "intr", required_argument, 0, 'i'}, + { "pinctrl", required_argument, 0, 'c'}, + { "testmode", required_argument, 0, 'm'}, + { 0, 0, 0, 0}, + }; + + while ((c = getopt_long(argc, argv, optstring, long_options, + NULL)) != -1) { + switch (c) { + case 'h': + ti.max_histogram = atoi(optarg); + break; + + case 'p': + ti.prio = atoi(optarg); + break; + + case 'l': + ti.max_cycles = atoi(optarg); + break; + + case 'q': + ti.quiet = 1; + break; + + case 'b': + ti.tracelimit = atoi(optarg); + break; + + case 'i': + ti.gpio_intr = atoi(optarg); + break; + + case 'o': + ti.gpio_out = atoi(optarg); + break; + + case 'c': + strcpy(ti.pin_controller, optarg); + break; + + case 'm': + ti.mode = atoi(optarg) >= + MODE_REACT ? MODE_REACT : MODE_LOOPBACK; + break; + + default: + display_help(); + exit(2); + } + } + + if ((ti.gpio_out == -1) || (ti.gpio_intr == -1) + || (strlen(ti.pin_controller) == 0)) { + display_help(); + exit(2); + } + + ti.prio = ti.prio > DEFAULT_PRIO ? DEFAULT_PRIO : ti.prio; + ti.max_cycles = ti.max_cycles > MAX_CYCLES ? MAX_CYCLES : ti.max_cycles; + + ti.max_histogram = ti.max_histogram > MAX_HIST ? + MAX_HIST : ti.max_histogram; + ti.ts.inner_hist_array = calloc(ti.max_histogram, sizeof(long)); + ti.ts.outer_hist_array = calloc(ti.max_histogram, sizeof(long)); +} + +static int thread_msleep(unsigned int ms) +{ + struct timespec ts = { + .tv_sec = (ms * NS_PER_MS) / NS_PER_S, + .tv_nsec = (ms * NS_PER_MS) % NS_PER_S, + }; + + return -nanosleep(&ts, NULL); +} + +static inline int64_t calc_us(struct timespec t) +{ + return (t.tv_sec * NS_PER_S + t.tv_nsec); +} + +static int setevent(char *event, char *val) +{ + int fd; + int ret; + + fd = open(event, O_WRONLY); + if (fd < 0) { + printf("unable to open %s\n", event); + return -1; + } + + ret = write(fd, val, strlen(val)); + if (ret < 0) { + printf("unable to write %s to %s\n", val, event); + close(fd); + return -1; + } + + close(fd); + return 0; +} + +static void tracing(char *enable) +{ + setevent(TRACING_EVENTS, enable); + setevent(TRACING_ON, enable); +} + +#define write_check(__fd, __buf, __len) \ + do { \ + int __ret = write(__fd, __buf, __len); \ + (void)__ret; \ + } while (0) + +#define TRACEBUFSIZ 1024 +static __thread char tracebuf[TRACEBUFSIZ]; + +static void tracemark(char *fmt, ...) __attribute__((format(printf, 1, 2))); +static void tracemark(char *fmt, ...) +{ + va_list ap; + int len; + int tracemark_fd; + + tracemark_fd = open(TRACE_MARKER, O_WRONLY); + if (tracemark_fd == -1) { + printf("unable to open trace_marker file: %s\n", TRACE_MARKER); + return; + } + + /* bail out if we're not tracing */ + /* or if the kernel doesn't support trace_mark */ + if (tracemark_fd < 0) + return; + + va_start(ap, fmt); + len = vsnprintf(tracebuf, TRACEBUFSIZ, fmt, ap); + va_end(ap); + write_check(tracemark_fd, tracebuf, len); + + close(tracemark_fd); +} + +static int rw_gpio(int value, int index) +{ + int ret; + struct timespec timestamp; + struct rtdm_gpio_readout rdo; + uint64_t gpio_write, gpio_read, inner_diff, outer_diff; + + clock_gettime(CLOCK_MONOTONIC, ×tamp); + gpio_write = calc_us(timestamp); + + ret = write(ti.fd_dev_out, &value, sizeof(value)); + if (ret < 0) { + printf("write GPIO, failed\n"); + return ret; + } + + ret = read(ti.fd_dev_intr, &rdo, sizeof(struct rtdm_gpio_readout)); + if (ret < 0) { + printf("read GPIO, failed\n"); + return ret; + } + + clock_gettime(CLOCK_MONOTONIC, ×tamp); + gpio_read = calc_us(timestamp); + + inner_diff = (rdo.timestamp - gpio_write) / 1000; + outer_diff = (gpio_read - gpio_write) / 1000; + + if (inner_diff < ti.ts.inner_min) + ti.ts.inner_min = inner_diff; + if (inner_diff > ti.ts.inner_max) + ti.ts.inner_max = inner_diff; + ti.ts.inner_avg += (double) inner_diff; + if (inner_diff >= ti.max_histogram) + ti.ts.inner_hist_overflow++; + else + ti.ts.inner_hist_array[inner_diff]++; + + if (outer_diff < ti.ts.outer_min) + ti.ts.outer_min = outer_diff; + if (inner_diff > ti.ts.outer_max) + ti.ts.outer_max = outer_diff; + ti.ts.outer_avg += (double) outer_diff; + if (outer_diff >= ti.max_histogram) + ti.ts.outer_hist_overflow++; + else + ti.ts.outer_hist_array[outer_diff]++; + + if (ti.quiet == 0) + printf("index: %d, inner_diff: %8ld, outer_diff: %8ld\n", + index, inner_diff, outer_diff); + + return outer_diff; +} + +static void *run_gpiobench_loop(void *cookie) +{ + int i, ret; + + printf("----rt task, gpio loop, test run----\n"); + + for (i = 0; i < ti.max_cycles; i++) { + ti.total_cycles = i; + /* send a high level pulse from gpio output pin and + * receive an interrupt from the other gpio pin, + * measuring the time elapsed between the two events + */ + ret = rw_gpio(GPIO_HIGH, i); + if (ret < 0) { + printf("RW GPIO, failed\n"); + break; + } else if (ret > ti.tracelimit) { + tracemark("hit latency threshold (%d > %d), index: %d", + ret, ti.tracelimit, i); + break; + } + + /*take a break, nanosleep here will not jeopardize the latency*/ + thread_msleep(10); + + ret = rw_gpio(GPIO_LOW, i); + /* send a low level pulse from gpio output pin and + * receive an interrupt from the other gpio pin, + * measuring the time elapsed between the two events + */ + if (ret < 0) { + printf("RW GPIO, failed\n"); + break; + } else if (ti.tracelimit && ret > ti.tracelimit) { + tracemark("hit latency threshold (%d > %d), index: %d", + ret, ti.tracelimit, i); + break; + } + + /*take a break, nanosleep here will not jeopardize the latency*/ + thread_msleep(10); + } + + ti.ts.inner_avg /= (ti.total_cycles * 2); + ti.ts.outer_avg /= (ti.total_cycles * 2); + + return NULL; +} + +static void *run_gpiobench_react(void *cookie) +{ + int value, ret, i; + struct rtdm_gpio_readout rdo; + + printf("----rt task, gpio react, test run----\n"); + + for (i = 0; i < ti.max_cycles; i++) { + /* received a pulse from latency box from one of + * the gpio pin pair + */ + ret = read(ti.fd_dev_intr, &rdo, sizeof(rdo)); + if (ret < 0) { + printf("RW GPIO read, failed\n"); + break; + } + + if (ti.quiet == 0) + printf("idx: %d, received signal from latency box\n", + i); + + /* send a signal back from the other gpio pin + * to latency box as the acknowledge, + * latency box will measure the time elapsed + * between the two events + */ + value = GPIO_HIGH; + ret = write(ti.fd_dev_out, &value, sizeof(value)); + if (ret < 0) { + printf("RW GPIO write, failed\n"); + break; + } + + if (ti.quiet == 0) + printf("idx: %d, sent reaction to latency box\n", i); + } + + return NULL; +} + +static void setup_sched_parameters(pthread_attr_t *attr, int prio) +{ + struct sched_param p; + int ret; + + ret = pthread_attr_init(attr); + if (ret) { + printf("pthread_attr_init(), failed\n"); + return; + } + + ret = pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED); + if (ret) { + printf("pthread_attr_setinheritsched(), failed\n"); + return; + } + + ret = pthread_attr_setschedpolicy(attr, + prio ? SCHED_FIFO : SCHED_OTHER); + if (ret) { + printf("pthread_attr_setschedpolicy(), failed\n"); + return; + } + + p.sched_priority = prio; + ret = pthread_attr_setschedparam(attr, &p); + if (ret) { + printf("pthread_attr_setschedparam(), failed\n"); + return; + } +} + +static void init_ti(void) +{ + memset(&ti, 0, sizeof(struct test_info)); + ti.prio = DEFAULT_PRIO; + ti.max_cycles = MAX_CYCLES; + ti.total_cycles = MAX_CYCLES; + ti.max_histogram = MAX_HIST; + ti.tracelimit = DEFAULT_LIMIT; + ti.quiet = 0; + ti.gpio_out = -1; + ti.gpio_intr = -1; + ti.mode = MODE_LOOPBACK; + + ti.ts.inner_min = ti.ts.outer_min = DEFAULT_LIMIT; + ti.ts.inner_max = ti.ts.outer_max = 0; + ti.ts.inner_avg = ti.ts.outer_avg = 0.0; +} + +static void print_hist(void) +{ + int i; + + printf("\n"); + printf("# Inner Loop Histogram\n"); + printf("# Inner Loop latency is the latency in kernel space\n" + "# between gpio_set_value and irq handler\n"); + + for (i = 0; i < ti.max_histogram; i++) { + unsigned long curr_latency = ti.ts.inner_hist_array[i]; + + printf("%06d ", i); + printf("%06lu\n", curr_latency); + } + + printf("# Total:"); + printf(" %09lu", ti.total_cycles); + printf("\n"); + + printf("# Min Latencies:"); + printf(" %05lu", ti.ts.inner_min); + printf("\n"); + printf("# Avg Latencies:"); + printf(" %05lf", ti.ts.inner_avg); + printf("\n"); + printf("# Max Latencies:"); + printf(" %05lu", ti.ts.inner_max); + printf("\n"); + + printf("\n"); + printf("\n"); + + printf("# Outer Loop Histogram\n"); + printf("# Outer Loop latency is the latency in user space\n" + "# between write and read\n" + "# Technically, outer loop latency is inner loop latercy\n" + "# plus overhead of event wakeup\n"); + + for (i = 0; i < ti.max_histogram; i++) { + unsigned long curr_latency = ti.ts.outer_hist_array[i]; + + printf("%06d ", i); + printf("%06lu\n", curr_latency); + } + + printf("# Total:"); + printf(" %09lu", ti.total_cycles); + printf("\n"); + + printf("# Min Latencies:"); + printf(" %05lu", ti.ts.outer_min); + printf("\n"); + printf("# Avg Latencies:"); + printf(" %05lf", ti.ts.outer_avg); + printf("\n"); + printf("# Max Latencies:"); + printf(" %05lu", ti.ts.outer_max); + printf("\n"); +} + +static void cleanup(void) +{ + int ret; + + if (ti.tracelimit < DEFAULT_LIMIT) + tracing(OFF); + + ret = close(ti.fd_dev_out); + if (ret < 0) + printf("can't close gpio_out device\n"); + + ret = close(ti.fd_dev_intr); + if (ret < 0) + printf("can't close gpio_intr device\n"); + + if (ti.mode == MODE_LOOPBACK) + print_hist(); + +} + +static void cleanup_and_exit(int sig) +{ + printf("Signal %d received\n", sig); + cleanup(); + exit(0); +} + +int main(int argc, char **argv) +{ + struct sigaction sa __attribute__((unused)); + int ret = 0; + pthread_attr_t tattr; + int trigger, value; + char dev_name[64]; + + init_ti(); + + process_options(argc, argv); + + ret = mlockall(MCL_CURRENT|MCL_FUTURE); + if (ret) { + printf("mlockall failed\n"); + goto out; + } + + sprintf(dev_name, "%s%s/gpio%d", + DEV_PATH, ti.pin_controller, ti.gpio_out); + ti.fd_dev_out = open(dev_name, O_RDWR); + if (ti.fd_dev_out < 0) { + printf("can't open %s\n", dev_name); + goto out; + } + + if (ti.gpio_out) { + value = 0; + ret = ioctl(ti.fd_dev_out, GPIO_RTIOC_DIR_OUT, &value); + if (ret) { + printf("ioctl gpio port output, failed\n"); + goto out; + } + } + + sprintf(dev_name, "%s%s/gpio%d", + DEV_PATH, ti.pin_controller, ti.gpio_intr); + ti.fd_dev_intr = open(dev_name, O_RDWR); + if (ti.fd_dev_intr < 0) { + printf("can't open %s\n", dev_name); + goto out; + } + + if (ti.gpio_intr) { + trigger = GPIO_TRIGGER_EDGE_FALLING|GPIO_TRIGGER_EDGE_RISING; + value = 1; + + ret = ioctl(ti.fd_dev_intr, GPIO_RTIOC_IRQEN, &trigger); + if (ret) { + printf("ioctl gpio port interrupt, failed\n"); + goto out; + } + + ret = ioctl(ti.fd_dev_intr, GPIO_RTIOC_TS, &value); + if (ret) { + printf("ioctl gpio port ts, failed\n"); + goto out; + } + } + + if (ti.tracelimit < DEFAULT_LIMIT) + tracing(ON); + + signal(SIGTERM, cleanup_and_exit); + signal(SIGINT, cleanup_and_exit); + setup_sched_parameters(&tattr, ti.prio); + + if (ti.mode == MODE_LOOPBACK) + ret = pthread_create(&ti.gpio_task, &tattr, + run_gpiobench_loop, NULL); + else + ret = pthread_create(&ti.gpio_task, &tattr, + run_gpiobench_react, NULL); + + if (ret) { + printf("pthread_create(gpiotask), failed\n"); + goto out; + } + + pthread_join(ti.gpio_task, NULL); + pthread_attr_destroy(&tattr); + +out: + cleanup(); + return 0; +} -- 2.7.4
