/* * Copyright (C) 2020 Song Chen * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 clockid; 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 \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" "-k --clockid 0 is CLOCK_REALTIME\n" " 1 is CLOCK_MONOTONIC,\n" " default=1\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:k: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'}, { "clockid", required_argument, 0, 'k'}, { 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; case 'k': ti.clockid = atoi(optarg) >= CLOCK_MONOTONIC ? CLOCK_MONOTONIC : CLOCK_REALTIME; 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 long long calc_us(struct timespec t) { return ((long long)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; long long gpio_write, gpio_read, inner_diff, outer_diff; clock_gettime(ti.clockid, ×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(ti.clockid, ×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: %8lld, outer_diff: %8lld\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.clockid = CLOCK_MONOTONIC; 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; if (ti.total_cycles < (ti.max_cycles - 1)) { /* * Test is interrupted. Force to calculate * even though it is not accurate but to avoid * large latency surprising us. */ printf("\nTest is interrupted and exit exceptionally\n"); printf("Please run again till it exit normally\n"); ti.ts.inner_avg /= (ti.total_cycles * 2); ti.ts.outer_avg /= (ti.total_cycles * 2); } 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, ti.clockid == CLOCK_MONOTONIC ? GPIO_RTIOC_TS_MONO : GPIO_RTIOC_TS_REAL, &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; }