Measure response time to GPIO events sent by a remote Zephyr-based latency monitor (latmon). This monitor collects then sends the latency data over a network connection to the latmus front-end which displays the results received:
[Linux device under test running latmus] <--+ | ^ | (GPIO ack) | | | | TCP/IP network connection | | | (control & data samples) | (GPIO pulse) v | | [Zephyr-based device running latmon] <--+ The generic latency monitor code running the measurement loop is available from the https://git.evlproject.org/libevl.git/tree/benchmarks/zephyr directory. This support comes with a Zephyr device tree patch for enabling this monitor on NXP's FRDM-K64F development board. Signed-off-by: hongzha1 <hongzhan.c...@intel.com> --- configure.ac | 1 + doc/asciidoc/Makefile.am | 2 + doc/asciidoc/man1/latmus.adoc | 112 +++ include/rtdm/uapi/testing.h | 16 + testsuite/Makefile.am | 4 +- testsuite/latmus/Makefile.am | 18 + testsuite/latmus/latmus.c | 1446 +++++++++++++++++++++++++++++++++ 7 files changed, 1598 insertions(+), 1 deletion(-) create mode 100644 doc/asciidoc/man1/latmus.adoc create mode 100644 testsuite/latmus/Makefile.am create mode 100644 testsuite/latmus/latmus.c diff --git a/configure.ac b/configure.ac index bd5fd5ba9..e8516e103 100644 --- a/configure.ac +++ b/configure.ac @@ -954,6 +954,7 @@ AC_CONFIG_FILES([ \ lib/trank/Makefile \ testsuite/Makefile \ testsuite/latency/Makefile \ + testsuite/latmus/Makefile \ testsuite/switchtest/Makefile \ testsuite/gpiotest/Makefile \ testsuite/gpiobench/Makefile \ diff --git a/doc/asciidoc/Makefile.am b/doc/asciidoc/Makefile.am index 8e33c7281..cd25931ee 100644 --- a/doc/asciidoc/Makefile.am +++ b/doc/asciidoc/Makefile.am @@ -10,6 +10,7 @@ HTML_DOCS = \ html/man1/clocktest \ html/man1/corectl \ html/man1/dohell \ + html/man1/latmus \ html/man1/latency \ html/man1/rtcanconfig \ html/man1/rtcanrecv \ @@ -40,6 +41,7 @@ MAN1_DOCS = \ man1/corectl.1 \ man1/cyclictest.1 \ man1/dohell.1 \ + man1/latmus.1 \ man1/latency.1 \ man1/rtcanconfig.1 \ man1/rtcanrecv.1 \ diff --git a/doc/asciidoc/man1/latmus.adoc b/doc/asciidoc/man1/latmus.adoc new file mode 100644 index 000000000..8414ed90f --- /dev/null +++ b/doc/asciidoc/man1/latmus.adoc @@ -0,0 +1,112 @@ +// ** The above line should force tbl to be a preprocessor ** +// Man page for latmus +// +// Copyright (C) 2021 Hongzhan Chen <hongzhan.c...@intel.com> +// +// You may distribute under the terms of the GNU General Public +// License as specified in the file COPYING that comes with the +// Xenomai distribution. +// +// +SWITCHTEST(1) +============= +:doctype: manpage +:revdate: 2021/04/20 +:man source: Xenomai +:man version: {xenover} +:man manual: Xenomai Manual + +NAME +----- +latmus - Measuring response time to interrupts + +SYNOPSIS +--------- +// The general command line +*latmus* [options] + +DESCRIPTION +------------ +*latmus* is part of Xenomai. It can be used to measure response time to +interrupts such as response time to timer events and response time to GPIO +events. A suitable Xenomai enabled kernel with the respective module +(xeno_latmus) must be installed. + +OPTIONS +-------- + +*latmus* accepts the following options: + +*--measure, -m*:: +measure latency on timer event [default] + +*--tune, -t*:: +tune the Xenomai core timer + +*--irq, -i*:: +measure/tune interrupt latency + +*--kernel, -k*:: +measure/tune kernel scheduling latency + +*--user, -u*:: +measure/tune user scheduling latency + +*--sirq, -s*:: +measure in-band response time to synthetic irq + +*--period=<us>, -s*:: +sampling period + +*--cpu=<n>, -c*:: +pin responder thread to CPU [=current] + +*--force-cpu=<n>, -C*:: +similar to -c, accept non-isolated CPU + +*--reset, -r*:: +reset core timer gravity to factory default + +*--background, -b*:: +run in the background (daemon mode) + +*--keep-going, -K*:: +keep going on unexpected switch to in-band mode + +*--max-abort=<us>, -A*:: +abort if maximum latency exceeds threshold + +*--timeout=<t>[dhms], -T*:: +stop measurement after <t> [d(ays)|h(ours)|m(inutes)|s(econds)] + +*--verbose[=level], -v*:: +set verbosity level [=1] + +*--quiet, -q*:: +quiet mode (i.e. --verbose=0) + +*--lines=<num>, -l*:: +result lines per page, 0 = no pagination [=21] + +*--histogram[=<nr>], -H*:: +set histogram size to <nr> cells [=200] + +*--plot=<filename>, -g*:: +dump histogram data to file (gnuplot format) + +*--oob-gpio=<host>, -Z*:: +measure Xenomai response time to GPIO event via <host> + +*--inband-gpio=<host>, -z*:: +measure in-band response time to GPIO event via <host> + +*--gpio-in=<spec>, -I*:: +input GPIO line configuration with <spec> = gpiochip-devname,pin-number[,rising-edge|falling-edge] + +*--gpio-out=<spec>, -O*:: +output GPIO line configuration with <spec> = gpiochip-devname,pin-number + +AUTHOR +------- +*latmus* was written by Philippe Gerum and ported by Hongzhan Chen from +evl project. This man page was written by Hongzhan Chen. diff --git a/include/rtdm/uapi/testing.h b/include/rtdm/uapi/testing.h index b1723b9f8..d7e2707e1 100644 --- a/include/rtdm/uapi/testing.h +++ b/include/rtdm/uapi/testing.h @@ -256,6 +256,22 @@ struct latmus_result { #define RTTST_RTIOC_COBALT_LATIOC_RESET \ _IO(RTIOC_TYPE_TESTING, 0x54) +#define LATMON_NET_PORT 2306 + +struct latmon_net_request { + uint32_t period_usecs; + uint32_t histogram_cells; +} __attribute__((__packed__)); + +struct latmon_net_data { + int32_t sum_lat_hi; + int32_t sum_lat_lo; + int32_t min_lat; + int32_t max_lat; + uint32_t overruns; + uint32_t samples; +} __attribute__((__packed__)); + /** @} */ #endif /* !_RTDM_UAPI_TESTING_H */ diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am index 4932f6d33..4baff2f19 100644 --- a/testsuite/Makefile.am +++ b/testsuite/Makefile.am @@ -1,10 +1,11 @@ -SUBDIRS = latency smokey gpiobench +SUBDIRS = latency latmus smokey gpiobench if XENO_COBALT SUBDIRS += \ clocktest \ gpiotest \ + latmus \ spitest \ switchtest \ xeno-test @@ -15,6 +16,7 @@ DIST_SUBDIRS = \ gpiotest \ gpiobench \ latency \ + latmus \ smokey \ spitest \ switchtest \ diff --git a/testsuite/latmus/Makefile.am b/testsuite/latmus/Makefile.am new file mode 100644 index 000000000..8eab3d740 --- /dev/null +++ b/testsuite/latmus/Makefile.am @@ -0,0 +1,18 @@ +testdir = @XENO_TEST_DIR@ + +CCLD = $(top_srcdir)/scripts/wrap-link.sh $(CC) + +test_PROGRAMS = latmus + +latmus_SOURCES = latmus.c + +latmus_CPPFLAGS = \ + $(XENO_USER_CFLAGS) \ + -I$(top_srcdir)/include + +latmus_LDFLAGS = @XENO_AUTOINIT_LDFLAGS@ $(XENO_POSIX_WRAPPERS) + +latmus_LDADD = \ + @XENO_CORE_LDADD@ \ + @XENO_USER_LDADD@ \ + -lpthread -lrt -lm diff --git a/testsuite/latmus/latmus.c b/testsuite/latmus/latmus.c new file mode 100644 index 000000000..016984444 --- /dev/null +++ b/testsuite/latmus/latmus.c @@ -0,0 +1,1446 @@ +/* + * SPDX-License-Identifier: MIT + * + * Derived from Xenomai Cobalt's latency & autotune utilities + * (http://git.xenomai.org/xenomai-3.git/) + * Copyright (C) 2014 Gilles Chanteperdrix <g...@xenomai.org> + * Copyright (C) 2018-2020 Philippe Gerum <r...@xenomai.org> + */ + +#include <fcntl.h> +#include <unistd.h> +#include <stdint.h> +#include <getopt.h> +#include <netdb.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <pthread.h> +#include <semaphore.h> +#include <limits.h> +#include <time.h> +#include <string.h> +#include <memory.h> +#include <poll.h> +#include <signal.h> +#include <error.h> +#include <errno.h> +#include <unistd.h> +#include <ctype.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <linux/gpio.h> +#include <rtdm/testing.h> +#include <rtdm/gpio.h> + +#define COBALT_CPU_OOB (1 << 0) +#define COBALT_CPU_ISOL (1 << 1) +#define COBALT_CPU_OFFLINE (1 << 2) + +#define ISOLATED_CPU_LIST "/sys/devices/system/cpu/isolated" +#define OOB_CPU_LIST "/sys/module/xenomai/parameters/supported_cpus" + +static cpu_set_t isolated_cpus; + +#define OOB_GPIO_LAT 1 +#define INBAND_GPIO_LAT 2 + +#define LATMON_TIMEOUT_SECS 5 + +static int test_irqlat, test_klat, + test_ulat, test_sirqlat, + test_gpiolat; + +static bool reset, background, + abort_on_switch = true; + +static int verbosity = 1, + abort_threshold; + +static bool tuning; + +static time_t timeout; + +static time_t start_time; + +static unsigned int period_usecs = 1000; /* 1ms */ + +static int responder_priority = 98; + +static int responder_cpu = -1; + +static int responder_cpu_state; + +static struct in_addr gpio_monitor_ip; + +static sigset_t sigmask; + +static int latmus_fd = -1; + +static int gpio_infd = -1, gpio_outfd = -1; + +static int gpio_inpin, gpio_outpin; + +static int gpio_hdinflags = GPIOHANDLE_REQUEST_INPUT, + gpio_hdoutflags = GPIOHANDLE_REQUEST_OUTPUT; + +static int gpio_evinflags; + +static pthread_t responder, logger; + +static sem_t logger_done; + +static bool c_state_restricted; + +static bool force_cpu; + +#define short_optlist "ikusrqbKmtp:A:T:v::l:g::H:P:c:Z:z:I:O:C:" + +static const struct option options[] = { + { + .name = "irq", + .has_arg = no_argument, + .val = 'i' + }, + { + .name = "kernel", + .has_arg = no_argument, + .val = 'k' + }, + { + .name = "user", + .has_arg = no_argument, + .val = 'u' + }, + { + .name = "sirq", + .has_arg = no_argument, + .val = 's' + }, + { + .name = "reset", + .has_arg = no_argument, + .val = 'r' + }, + { + .name = "quiet", + .has_arg = no_argument, + .val = 'q' + }, + { + .name = "background", + .has_arg = no_argument, + .val = 'b' + }, + { + .name = "keep-going", + .has_arg = no_argument, + .val = 'K' + }, + { + .name = "measure", + .has_arg = no_argument, + .val = 'm', + }, + { + .name = "tune", + .has_arg = no_argument, + .val = 't', + }, + { + .name = "period", + .has_arg = required_argument, + .val = 'p', + }, + { + .name = "timeout", + .has_arg = required_argument, + .val = 'T', + }, + { + .name = "maxlat-abort", + .has_arg = required_argument, + .val = 'A', + }, + { + .name = "verbose", + .has_arg = optional_argument, + .val = 'v', + }, + { + .name = "lines", + .has_arg = required_argument, + .val = 'l', + }, + { + .name = "plot", + .has_arg = optional_argument, + .val = 'g', + }, + { + .name = "histogram", + .has_arg = required_argument, + .val = 'H', + }, + { + .name = "priority", + .has_arg = required_argument, + .val = 'P', + }, + { + .name = "cpu", + .has_arg = required_argument, + .val = 'c', + }, + { + .name = "force-cpu", + .has_arg = required_argument, + .val = 'C', + }, + { + .name = "oob-gpio", + .has_arg = required_argument, + .val = 'Z', + }, + { + .name = "inband-gpio", + .has_arg = required_argument, + .val = 'z', + }, + { + .name = "gpio-in", + .has_arg = required_argument, + .val = 'I', + }, + { + .name = "gpio-out", + .has_arg = required_argument, + .val = 'O', + }, + { /* Sentinel */ } +}; + +static inline unsigned int stack_size(unsigned int size) +{ + return size > PTHREAD_STACK_MIN ? size : PTHREAD_STACK_MIN; +} + +static void create_responder(pthread_t *tid, void *(*responder)(void *)) +{ + struct sched_param param; + pthread_attr_t attr; + int ret; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedpolicy(&attr, SCHED_FIFO); + param.sched_priority = responder_priority; + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setstacksize(&attr, stack_size(65536)); + ret = pthread_create(tid, &attr, responder, NULL); + pthread_attr_destroy(&attr); + if (ret) + error(1, ret, "sampling thread"); +} + +#define ONE_BILLION 1000000000 +#define TEN_MILLIONS 10000000 + +static int lat_xfd = -1; + +static int lat_sock = -1; + +static int data_lines = 21; + +static int32_t *histogram; + +static unsigned int histogram_cells = 200; + +static struct latmus_measurement last_bulk; + +static unsigned int all_overruns; + +static unsigned int spurious_inband_switches; + +static int32_t all_minlat = TEN_MILLIONS, all_maxlat = -TEN_MILLIONS; + +static int64_t all_sum; + +static int64_t all_samples; + +static time_t peak_time; + +static FILE *plot_fp; + +#define COBALT_LAT_OOB_GPIO (COBALT_LAT_LAST + 1) +#define COBALT_LAT_INBAND_GPIO (COBALT_LAT_OOB_GPIO + 1) + +static int context_type = COBALT_LAT_USER; + +const char *context_labels[] = { + [COBALT_LAT_IRQ] = "irq", + [COBALT_LAT_SIRQ] = "sirq", + [COBALT_LAT_KERN] = "kernel", + [COBALT_LAT_USER] = "user", + [COBALT_LAT_OOB_GPIO] = "oob-gpio", + [COBALT_LAT_INBAND_GPIO] = "inband-gpio", +}; + +static void __log_results(struct latmus_measurement *meas) +{ + if (meas->min_lat < all_minlat) + all_minlat = meas->min_lat; + if (meas->max_lat > all_maxlat) { + peak_time = time(NULL) - start_time - 1; + all_maxlat = meas->max_lat; + if (abort_threshold && all_maxlat > abort_threshold) { + fprintf(stderr, "latency threshold is exceeded" + " (%d >= %.3f), aborting.\n", + abort_threshold, + (double)all_maxlat / 1000.0); + exit(102); + } + } + + all_sum += meas->sum_lat; + all_samples += meas->samples; + all_overruns += meas->overruns; +} + +static void log_results(struct latmus_measurement *meas, + unsigned int round) +{ + double min, avg, max, best, worst; + bool oops = false; + time_t now, dt; + + if (verbosity > 0 && data_lines && (round % data_lines) == 0) { + time(&now); + dt = now - start_time - 1; /* -1s warm-up time */ + printf("RTT| %.2ld:%.2ld:%.2ld (%s, %u us period,", + dt / 3600, (dt / 60) % 60, dt % 60, + context_labels[context_type], period_usecs); + if (context_type != COBALT_LAT_IRQ && context_type != COBALT_LAT_SIRQ) + printf(" priority %d,", responder_priority); + printf(" CPU%d%s)\n", + responder_cpu, + responder_cpu_state & COBALT_CPU_ISOL ? "" : "-noisol"); + printf("RTH|%11s|%11s|%11s|%8s|%6s|%11s|%11s\n", + "----lat min", "----lat avg", + "----lat max", "-overrun", "---msw", + "---lat best", "--lat worst"); + } + + __log_results(meas); + min = (double)meas->min_lat / 1000.0; + avg = (double)(meas->sum_lat / (int)meas->samples) / 1000.0; + max = (double)meas->max_lat / 1000.0; + best = (double)all_minlat / 1000.0; + worst = (double)all_maxlat / 1000.0; + + /* + * A trivial check on the reported values, so that we detect + * and stop on obviously inconsistent results. + */ + if (min > max || min > avg || avg > max || + min > worst || max > worst || avg > worst || + best > worst || worst < best) { + oops = true; + verbosity = 1; + } + + if (verbosity > 0) + printf("RTD|%11.3f|%11.3f|%11.3f|%8d|%6u|%11.3f|%11.3f\n", + min, avg, max, + all_overruns, spurious_inband_switches, + best, worst); + + if (max > 1000) + exit(103); + if (oops) { + fprintf(stderr, "results look weird, aborting.\n"); + exit(103); + } +} + +static inline void notify_start(void) +{ + if (timeout) + alarm(timeout + 1); /* +1 warm-up time */ +} + +static void create_logger(pthread_t *tid, void *(*logger)(void *), void *arg) +{ + struct sched_param param; + pthread_attr_t attr; + int ret; + + sem_init(&logger_done, 0, 0); + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedpolicy(&attr, SCHED_OTHER); + param.sched_priority = 0; + pthread_attr_setschedparam(&attr, ¶m); + pthread_attr_setstacksize(&attr, stack_size(65536)); + ret = pthread_create(tid, &attr, logger, arg); + pthread_attr_destroy(&attr); + if (ret) + error(1, ret, "logger thread"); +} + +static void *xbuf_logger_thread(void *arg) +{ + struct latmus_measurement meas; + ssize_t ret, round = 0; + + for (;;) { + ret = read(lat_xfd, &meas, sizeof(meas)); + if (ret != sizeof(meas)) + break; + log_results(&meas, round++); + } + + /* Nobody waits for logger_done in timer mode. */ + + return NULL; +} + +static void *timer_responder(void *arg) +{ + __u64 timestamp = 0; + struct timespec now; + int ret; + + for (;;) { + ret = ioctl(latmus_fd, RTTST_RTIOC_COBALT_LATIOC_PULSE, + ×tamp); + if (ret) { + if (errno != EPIPE) + error(1, errno, "pulse failed"); + timestamp = 0; /* Next period. */ + } else { + clock_gettime(CLOCK_MONOTONIC, &now); + timestamp = (__u64)now.tv_sec * 1000000000 + now.tv_nsec; + } + } + + return NULL; +} + +static void *timer_test_sitter(void *arg) +{ + struct latmus_measurement_result mr; + struct latmus_result result; + int ret; + + mr.last_ptr = (__u64)(uintptr_t)&last_bulk; + mr.histogram_ptr = (__u64)(uintptr_t)histogram; + mr.len = histogram ? histogram_cells * sizeof(int32_t) : 0; + + result.data_ptr = (__u64)(uintptr_t)&mr; + result.len = sizeof(mr); + + notify_start(); + + /* Run test until signal. */ + ret = ioctl(latmus_fd, RTTST_RTIOC_COBALT_LATIOC_RUN, &result); + if (ret) + error(1, errno, "measurement failed"); + + return NULL; +} + +static void setup_measurement_on_timer(void) +{ + struct latmus_setup setup; + pthread_attr_t attr; + pthread_t sitter; + int ret, sig; + char *rtp; + + memset(&setup, 0, sizeof(setup)); + setup.type = context_type; + setup.period = period_usecs * 1000ULL; /* ns */ + setup.priority = responder_priority; + setup.cpu = responder_cpu; + setup.u.measure.hcells = histogram ? histogram_cells : 0; + ret = ioctl(latmus_fd, RTTST_RTIOC_COBALT_LATIOC_MEASURE, &setup); + if (ret < 0) + error(1, errno, "measurement setup failed"); + + /*open xnpipe to exchange message*/ + if (asprintf(&rtp, "/dev/rtp%d", ret) < 0) + error(1, ENOMEM, "resolve rtp name"); + lat_xfd = open(rtp, O_RDWR); + if (lat_xfd < 0) + error(1, -lat_xfd, "cannot create rtp"); + free(rtp); + create_logger(&logger, xbuf_logger_thread, NULL); + + if (context_type == COBALT_LAT_USER) + create_responder(&responder, timer_responder); + + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, stack_size(65536)); + ret = pthread_create(&sitter, &attr, timer_test_sitter, NULL); + pthread_attr_destroy(&attr); + if (ret) + error(1, ret, "timer_test_sitter"); + + sigwait(&sigmask, &sig); + pthread_cancel(sitter); + pthread_join(sitter, NULL); + + /* + * Add results from the last incomplete bulk once the sitter + * has returned to user-space from oob_ioctl(COBALT_LATIOC_RUN) + * then exited, at which point such bulk contains meaningful + * data. + */ + if (last_bulk.samples > 0) + __log_results(&last_bulk); +} + +static void setup_gpio_pins(int *fds) +{ + int value; + int ret; + + ret = ioctl(gpio_infd, GPIO_RTIOC_IRQEN, &gpio_evinflags); + if (ret) + error(1, errno, "ioctl(GPIO_RTIOC_IRQEN)"); + + value = 1; + ret = ioctl(gpio_infd, GPIO_RTIOC_TS, &value); + if (ret) + error(1, errno, "ioctl(GPIO_RTIOC_TS)"); + + value = 1; + ret = ioctl(gpio_outfd, GPIO_RTIOC_DIR_OUT, &value); + if (ret) + error(1, errno, "ioctl(GPIO_RTIOC_DIR_OUT)"); + + fds[0] = gpio_infd; + fds[1] = gpio_outfd; +} + +static void *gpio_responder_thread(void *arg) +{ + int value; + struct rtdm_gpio_readout rdo; + const int ackval = 0; /* Remote observes falling edges. */ + int fds[2], ret; + + setup_gpio_pins(fds); + + for (;;) { + value = !ackval; + ret = write(fds[1], &value, sizeof(value)); + if (ret < 0) + error(1, errno, "write failed"); + + ret = read(fds[0], &rdo, + sizeof(struct rtdm_gpio_readout)); + if (ret != sizeof(struct rtdm_gpio_readout)) + break; + + value = ackval; + ret = write(fds[1], &value, sizeof(value)); + if (ret < 0) + error(1, errno, "write failed"); + } + + return NULL; +} + +static ssize_t read_net_data(void *buf, size_t len) +{ + ssize_t count = 0, ret; + struct pollfd pollfd; + + pollfd.fd = lat_sock; + pollfd.events = POLLIN; + pollfd.revents = 0; + + do { + /* Make sure to detect latmon unresponsivess. */ + ret = poll(&pollfd, 1, LATMON_TIMEOUT_SECS * 1000); + if (ret <= 0) + return -ETIMEDOUT; + ret = recv(lat_sock, buf + count, len - count, 0); + if (ret <= 0) + return ret; + count += ret; + } while (count < len); + + return count; +} + +static void *sock_logger_thread(void *arg) +{ + struct latmus_measurement meas; + struct latmon_net_data ndata; + bool *no_response = arg; + ssize_t ret, round = 0; + int cell; + + for (;;) { + ret = read_net_data(&ndata, sizeof(ndata)); + if (ret <= 0) + goto unresponsive; + + /* + * Receiving an empty data record means that we got + * the trailing data in the previous round, so + * we are done with sample bulks now. + */ + if (ndata.samples == 0) + break; + + /* This is valid sample data, log it. */ + meas.sum_lat = ((__s64)ntohl(ndata.sum_lat_hi)) << 32 | + ntohl(ndata.sum_lat_lo); + meas.min_lat = ntohl(ndata.min_lat); + meas.max_lat = ntohl(ndata.max_lat); + meas.overruns = ntohl(ndata.overruns); + meas.samples = ntohl(ndata.samples); + log_results(&meas, round++); + } + + if (histogram == NULL) + goto out; + + ret = read_net_data(histogram, histogram_cells * sizeof(int32_t)); + if (ret <= 0) { +unresponsive: + *no_response = true; + kill(getpid(), SIGHUP); + } else { + for (cell = 0; cell < histogram_cells; cell++) + histogram[cell] = ntohl(histogram[cell]); + } +out: + sem_post(&logger_done); + + return NULL; +} + +static void setup_measurement_on_gpio(void) +{ + struct latmon_net_request req; + struct sockaddr_in in_addr; + bool latmon_hung = false; + struct timespec timeout; + int ret, sig; + + lat_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (lat_sock < 0) + error(1, errno, "socket()"); + + if (verbosity) + printf("connecting to latmon at %s:%d...\n", + inet_ntoa(gpio_monitor_ip), LATMON_NET_PORT); + + memset(&in_addr, 0, sizeof(in_addr)); + in_addr.sin_family = AF_INET; + in_addr.sin_addr = gpio_monitor_ip; + in_addr.sin_port = htons(LATMON_NET_PORT); + ret = connect(lat_sock, (struct sockaddr *)&in_addr, + sizeof(in_addr)); + if (ret) + error(1, errno, "connect()"); + + if (verbosity && context_type == COBALT_LAT_INBAND_GPIO) + printf("CAUTION: measuring in-band response time (no COBALT there)\n"); + + create_responder(&responder, gpio_responder_thread); + create_logger(&logger, sock_logger_thread, &latmon_hung); + + notify_start(); + req.period_usecs = htonl(period_usecs); /* Non-zero, means start. */ + req.histogram_cells = histogram ? htonl(histogram_cells) : 0; + ret = send(lat_sock, &req, sizeof(req), 0); + if (ret != sizeof(req)) + error(1, errno, "send() start"); + + sigwait(&sigmask, &sig); + + /* + * From now on, we may wait up to LATMON_TIMEOUT_SECS + * max. between messages from the remote latency monitor + * before declaring it unresponsive. + */ + if (!latmon_hung) { + req.period_usecs = 0; /* Zero means stop. */ + req.histogram_cells = 0; + ret = send(lat_sock, &req, sizeof(req), 0); + if (ret != sizeof(req)) { + error(1, errno, "send() stop"); + latmon_hung = true; + } else { + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_sec += LATMON_TIMEOUT_SECS; + if (sem_timedwait(&logger_done, &timeout)) + latmon_hung = true; + } + } + + if (latmon_hung) + error(1, ETIMEDOUT, "latmon at %s is unresponsive", + inet_ntoa(gpio_monitor_ip)); + + close(lat_sock); +} + +static void paste_file_in(const char *path, const char *header) +{ + char buf[BUFSIZ]; + FILE *fp; + + fp = fopen(path, "r"); + if (fp == NULL) + return; + + fprintf(plot_fp, "# %s", header ?: ""); + + while (fgets(buf, sizeof(buf), fp)) + fputs(buf, plot_fp); + + fclose(fp); +} + +static void dump_gnuplot(time_t duration) +{ + int first, last, n; + + if (all_samples == 0) + return; + + fprintf(plot_fp, "# test started on: %s", ctime(&start_time)); + paste_file_in("/proc/version", NULL); + paste_file_in("/proc/cmdline", NULL); + //fprintf(plot_fp, "# libevl version: %s\n", evl_get_version().version_string); + fprintf(plot_fp, "# sampling period: %u microseconds\n", period_usecs); + paste_file_in("/sys/devices/virtual/clock/monotonic/gravity", + "clock gravity: "); + paste_file_in("/sys/devices/system/clocksource/clocksource0/current_clocksource", + "clocksource: "); + paste_file_in("/sys/devices/system/clocksource/clocksource0/vdso_clocksource", + "vDSO access: "); + fprintf(plot_fp, "# context: %s\n", context_labels[context_type]); + if (!(test_irqlat || test_sirqlat)) { + fprintf(plot_fp, "# thread priority: %d\n", responder_priority); + fprintf(plot_fp, "# thread affinity: CPU%d%s\n", + responder_cpu, + responder_cpu_state & COBALT_CPU_ISOL ? "" : "-noisol"); + } + if (c_state_restricted) + fprintf(plot_fp, "# C-state restricted\n"); + fprintf(plot_fp, "# duration (hhmmss): %.2ld:%.2ld:%.2ld\n", + duration / 3600, (duration / 60) % 60, duration % 60); + fprintf(plot_fp, "# peak (hhmmss): %.2ld:%.2ld:%.2ld\n", + peak_time / 3600, (peak_time / 60) % 60, peak_time % 60); + if (all_overruns > 0) + fprintf(plot_fp, "# OVERRUNS: %u\n", all_overruns); + if (spurious_inband_switches > 0) + fprintf(plot_fp, "# IN-BAND SWITCHES: %u\n", spurious_inband_switches); + fprintf(plot_fp, "# min latency: %.3f\n", + (double)all_minlat / 1000.0); + fprintf(plot_fp, "# avg latency: %.3f\n", + (double)(all_sum / all_samples) / 1000.0); + fprintf(plot_fp, "# max latency: %.3f\n", + (double)all_maxlat / 1000.0); + fprintf(plot_fp, "# sample count: %lld\n", + (long long)all_samples); + + for (n = 0; n < histogram_cells && histogram[n] == 0; n++) + ; + first = n; + + for (n = histogram_cells - 1; n >= 0 && histogram[n] == 0; n--) + ; + last = n; + + for (n = first; n < last; n++) + fprintf(plot_fp, "%d %d\n", n, histogram[n]); + + /* + * If we have outliers, display a '+' sign after the last cell + * index. + */ + fprintf(plot_fp, "%d%s %d\n", last, + all_maxlat / 1000 >= histogram_cells ? "+" : "", + histogram[last]); +} + +static void do_measurement(int type) +{ + const char *cpu_s = ""; + time_t duration; + + context_type = type; + + if (plot_fp) { + histogram = malloc(histogram_cells * sizeof(int32_t)); + if (histogram == NULL) + error(1, ENOMEM, "cannot get memory"); + } + + if (!(responder_cpu_state & COBALT_CPU_ISOL)) + cpu_s = " (not isolated)"; + + if (verbosity > 0) + fprintf(stderr, "warming up on CPU%d%s...\n", responder_cpu, cpu_s); + else + fprintf(stderr, "running quietly for %ld seconds on CPU%d%s\n", + timeout, responder_cpu, cpu_s); + + switch (type) { + case COBALT_LAT_OOB_GPIO: + case COBALT_LAT_INBAND_GPIO: + setup_measurement_on_gpio(); + break; + default: + setup_measurement_on_timer(); + } + + duration = time(NULL) - start_time - 1; /* -1s warm-up time */ + if (plot_fp) { + dump_gnuplot(duration); + if (plot_fp != stdout) + fclose(plot_fp); + free(histogram); + } + + if (!timeout) + timeout = duration; + + if (all_samples > 0) + printf("---|-----------|-----------|-----------|--------" + "|------|-------------------------\n" + "RTS|%11.3f|%11.3f|%11.3f|%8d|%6u| " + "%.2ld:%.2ld:%.2ld/%.2ld:%.2ld:%.2ld\n", + (double)all_minlat / 1000.0, + (double)(all_sum / all_samples) / 1000.0, + (double)all_maxlat / 1000.0, + all_overruns, spurious_inband_switches, + duration / 3600, (duration / 60) % 60, + duration % 60, duration / 3600, + (timeout / 60) % 60, timeout % 60); + + if (spurious_inband_switches > 0) { + if (all_samples > 0) + fputc('\n', stderr); + fprintf(stderr, "*** WARNING: unexpected switches to in-band mode detected,\n" + " latency figures displayed are NOT reliable.\n" + " Please submit a bug report upstream.\n"); + if (abort_on_switch) { + abort_on_switch = false; + fprintf(stderr, "*** OOPS: aborting upon spurious switch to in-band mode.\n"); + } + } + + if (responder) + pthread_cancel(responder); + + if (logger) + pthread_cancel(logger); +} + +static void do_tuning(int type) +{ + struct latmus_result result; + struct latmus_setup setup; + pthread_t responder; + __s32 gravity; + int ret; + + if (verbosity) { + printf("%s gravity...", context_labels[type]); + fflush(stdout); + } + + memset(&setup, 0, sizeof(setup)); + setup.type = type; + setup.period = period_usecs * 1000ULL; /* ns */ + setup.priority = responder_priority; + setup.cpu = responder_cpu; + setup.u.tune.verbosity = verbosity; + ret = ioctl(latmus_fd, RTTST_RTIOC_COBALT_LATIOC_TUNE, &setup); + if (ret) + error(1, errno, "tuning setup failed (%s)", context_labels[type]); + + if (type == COBALT_LAT_USER) + create_responder(&responder, timer_responder); + + pthread_sigmask(SIG_UNBLOCK, &sigmask, NULL); + + notify_start(); + + result.data_ptr = (__u64)(uintptr_t)&gravity; + result.len = sizeof(gravity); + ret = ioctl(latmus_fd, RTTST_RTIOC_COBALT_LATIOC_RUN, &result); + if (ret) + error(1, errno, "measurement failed"); + + if (type == COBALT_LAT_USER) + pthread_cancel(responder); + + if (verbosity) + printf("%u ns\n", gravity); +} + +#ifdef CONFIG_XENO_COBALT + +#include <cobalt/uapi/syscall.h> + +static void sigdebug_handler(int sig, siginfo_t *si, void *context) +{ + unsigned int reason = sigdebug_reason(si); + + if (reason > SIGDEBUG_WATCHDOG) + reason = SIGDEBUG_UNDEFINED; + + switch (reason) { + case SIGDEBUG_UNDEFINED: + case SIGDEBUG_NOMLOCK: + case SIGDEBUG_WATCHDOG: + exit(99); + } + + spurious_inband_switches++; + if (abort_on_switch) + kill(getpid(), SIGHUP); +} +#endif /* CONFIG_XENO_COBALT */ + +static void set_cpu_affinity(void) +{ + cpu_set_t cpu_set; + int ret; + + CPU_ZERO(&cpu_set); + CPU_SET(responder_cpu, &cpu_set); + ret = sched_setaffinity(0, sizeof(cpu_set), &cpu_set); + if (ret) + error(1, errno, "cannot set affinity to CPU%d", + responder_cpu); +} + +static void restrict_c_state(void) +{ + __s32 val = 0; + int fd; + + fd = open("/dev/cpu_dma_latency", O_WRONLY); + if (fd < 0) + return; + + if (write(fd, &val, sizeof(val) == sizeof(val))) + c_state_restricted = true; +} + +static void parse_host_spec(const char *host, struct in_addr *in_addr) +{ + struct addrinfo hints, *res; + int ret; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + ret = getaddrinfo(host, NULL, &hints, &res); + if (ret) + error(1, ret == EAI_SYSTEM ? errno : ESRCH, + "getaddrinfo(%s)", host); + + *in_addr = ((struct sockaddr_in *)res->ai_addr)->sin_addr; +} + +static int parse_gpio_spec(const char *spec, int *pin, + int *hdflags, int *evflags) +{ + char *s, *dev, *p, *endptr, *devname; + int fd, ret; + + s = strdup(spec); + dev = strtok(s, ","); + if (dev == NULL) + error(1, EINVAL, "no GPIO device in spec: %s", spec); + + p = strtok(NULL, ","); + if (p == NULL) + error(1, EINVAL, "no GPIO pin in spec: %s", spec); + + *pin = (int)strtol(p, &endptr, 10); + if (*pin < 0 || endptr == p) + error(1, EINVAL, "invalid GPIO pin number in spec: %s", + spec); + + ret = asprintf(&devname, "/dev/rtdm/%s/gpio%d", dev, *pin); + if (ret < 0) + error(1, ENOMEM, "asprintf()"); + + p = strtok(NULL, ","); + if (evflags) { + if (p) { + if (!strcmp(p, "rising-edge")) + *evflags = GPIO_TRIGGER_EDGE_FALLING; + else if (!strcmp(p, "falling-edge")) + *evflags = GPIO_TRIGGER_EDGE_RISING; + else + error(1, EINVAL, "invalid edge type in spec: %s", + spec); + } else /* Default is rising edge. */ + *evflags = GPIO_TRIGGER_EDGE_RISING; + } else if (p) + error(1, EINVAL, "trailing garbage in spec: %s", + spec); + + fd = open(devname, O_RDONLY); + if (fd < 0) + error(1, errno, "open(%s)", devname); + + free(devname); + free(s); + + return fd; +} + +static int get_realtime_cpu_set(cpu_set_t *cpuset) +{ + unsigned long long cpumask; + char buf[BUFSIZ], *p; + FILE *fp; + int cpu; + + fp = fopen("/sys/module/xenomai/parameters/supported_cpus", "r"); + if (fp == NULL) + return -ENOENT; + + p = fgets(buf, sizeof(buf), fp); + fclose(fp); + if (p == NULL) + return -EBADF; + + errno = 0; + cpumask = strtoll(p, NULL, 10); + if (cpumask == LLONG_MAX && errno == ERANGE) + cpumask = ULLONG_MAX; + for (cpu = 0; cpumask != 0; cpu++, cpumask >>= 1) { + if (cpumask & 1ULL) + CPU_SET(cpu, cpuset); + } + + return 0; +} + +static void parse_cpu_list(const char *path, cpu_set_t *cpuset) +{ + char *p, *range, *range_p = NULL, *id, *id_r; + int start, end, cpu; + char buf[BUFSIZ]; + FILE *fp; + + CPU_ZERO(cpuset); + + fp = fopen(path, "r"); + if (fp == NULL) + return; + + if (!fgets(buf, sizeof(buf), fp)) + goto out; + + p = buf; + while ((range = strtok_r(p, ",", &range_p)) != NULL) { + if (*range == '\0' || *range == '\n') + goto next; + end = -1; + id = strtok_r(range, "-", &id_r); + if (id) { + start = atoi(id); + id = strtok_r(NULL, "-", &id_r); + if (id) + end = atoi(id); + else if (end < 0) + end = start; + for (cpu = start; cpu <= end; cpu++) + CPU_SET(cpu, cpuset); + } +next: + p = NULL; + } +out: + fclose(fp); +} + +static void determine_responder_cpu(bool inband_test) +{ + cpu_set_t oob_cpus, best_cpus; + int cpu; + + parse_cpu_list(ISOLATED_CPU_LIST, &isolated_cpus); + get_realtime_cpu_set(&oob_cpus); + if (responder_cpu >= 0) { + if (!inband_test && !CPU_ISSET(responder_cpu, &oob_cpus)) { + if (verbosity) + printf("CPU%d is not OOB-capable, " + "picking a better one\n", + responder_cpu); + goto pick_oob; + } + goto finish; + } + + if (force_cpu) + goto finish; + + if (inband_test) + goto pick_isolated; + + /* + * Pick a default CPU among the ones which are both + * OOB-capable and isolated. If COBALT is not enabled, oob_cpus + * is empty so there is no best choice. + */ + CPU_AND(&best_cpus, &isolated_cpus, &oob_cpus); + for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { + if (CPU_ISSET(cpu, &best_cpus)) { + responder_cpu = cpu; + goto finish; + } + } + + /* + * If no best choice, pick the first OOB-capable CPU we can + * find (if any). + */ +pick_oob: + for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { + if (CPU_ISSET(cpu, &oob_cpus)) { + responder_cpu = cpu; + goto finish; + } + } + +pick_isolated: + /* + * This must be a kernel with no COBALT support or we + * specifically need an isolated CPU. + */ + for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { + if (CPU_ISSET(cpu, &isolated_cpus)) { + responder_cpu = cpu; + goto finish; + } + } + + /* Out of luck, run on the current CPU. */ + if (responder_cpu < 0) + responder_cpu = sched_getcpu(); +finish: + if (CPU_ISSET(responder_cpu, &isolated_cpus)) + responder_cpu_state = COBALT_CPU_ISOL; +} + +static void usage(void) +{ + fprintf(stderr, "usage: latmus [options]:\n"); + fprintf(stderr, "-m --measure measure latency on timer event [default]\n"); + fprintf(stderr, "-t --tune tune the COBALT core timer\n"); + fprintf(stderr, "-i --irq measure/tune interrupt latency\n"); + fprintf(stderr, "-k --kernel measure/tune kernel scheduling latency\n"); + fprintf(stderr, "-u --user measure/tune user scheduling latency\n"); + fprintf(stderr, " [ if none of --irq, --kernel or --user is given,\n" + " tune for all contexts ]\n"); + fprintf(stderr, "-s --sirq measure in-band response time to synthetic irq\n"); + fprintf(stderr, "-p --period=<us> sampling period\n"); + fprintf(stderr, "-P --priority=<prio> responder thread priority [=90]\n"); + fprintf(stderr, "-c --cpu=<n> pin responder thread to CPU [=current]\n"); + fprintf(stderr, "-C --force-cpu=<n> similar to -c, accept non-isolated CPU\n"); + fprintf(stderr, "-r --reset reset core timer gravity to factory default\n"); + fprintf(stderr, "-b --background run in the background (daemon mode)\n"); + fprintf(stderr, "-K --keep-going keep going on unexpected switch to in-band mode\n"); + fprintf(stderr, "-A --max-abort=<us> abort if maximum latency exceeds threshold\n"); + fprintf(stderr, "-T --timeout=<t>[dhms] stop measurement after <t> [d(ays)|h(ours)|m(inutes)|s(econds)]\n"); + fprintf(stderr, "-v --verbose[=level] set verbosity level [=1]\n"); + fprintf(stderr, "-q --quiet quiet mode (i.e. --verbose=0)\n"); + fprintf(stderr, "-l --lines=<num> result lines per page, 0 = no pagination [=21]\n"); + fprintf(stderr, "-H --histogram[=<nr>] set histogram size to <nr> cells [=200]\n"); + fprintf(stderr, "-g --plot=<filename> dump histogram data to file (gnuplot format)\n"); + fprintf(stderr, "-Z --oob-gpio=<host> measure COBALT response time to GPIO event via <host>\n"); + fprintf(stderr, "-z --inband-gpio=<host> measure in-band response time to GPIO event via <host>\n"); + fprintf(stderr, "-I --gpio-in=<spec> input GPIO line configuration\n"); + fprintf(stderr, " with <spec> = gpiochip-devname,pin-number[,rising-edge|falling-edge]\n"); + fprintf(stderr, "-O --gpio-out=<spec> output GPIO line configuration\n"); + fprintf(stderr, " with <spec> = gpiochip-devname,pin-number\n"); +} + +static void bad_usage(int argc, char *const argv[]) +{ + int last = optind < argc ? optind : argc - 1; + + printf("** Uh, you lost me somewhere near '%s' (arg #%d)\n", argv[last], last); + usage(); +} + +int main(int argc, char *const argv[]) +{ + int ret, c, spec, type, max_prio, lindex; + const char *plot_filename = NULL; + struct sigaction sa; + char *endptr; + + opterr = 0; + + for (;;) { + c = getopt_long(argc, argv, short_optlist, options, &lindex); + if (c == EOF) + break; + + switch (c) { + case 0: + break; + case 'i': + test_irqlat = 1; + break; + case 'k': + test_klat = 1; + break; + case 'u': + test_ulat = 1; + break; + case 's': + test_sirqlat = 1; + break; + case 'r': + reset = true; + break; + case 'q': + verbosity = 0; + break; + case 'b': + background = true; + break; + case 'K': + abort_on_switch = false; + break; + case 'm': + tuning = false; + break; + case 't': + tuning = true; + break; + case 'p': + period_usecs = atoi(optarg); + if (period_usecs <= 0 || period_usecs > 1000000) + error(1, EINVAL, "invalid sampling period " + "(0 < period < 1000000)"); + break; + case 'A': + abort_threshold = atoi(optarg) * 1000; /* ns */ + if (abort_threshold <= 0) + error(1, EINVAL, "invalid timeout"); + break; + case 'T': + timeout = (int)strtol(optarg, &endptr, 10); + if (timeout < 0 || endptr == optarg) + error(1, EINVAL, "invalid timeout"); + switch (*endptr) { + case 'd': + timeout *= 24; + /* Falldown wanted. */ + case 'h': + timeout *= 60; + /* Falldown wanted. */ + case 'm': + timeout *= 60; + break; + case 's': + case '\0': + break; + default: + error(1, EINVAL, "invalid time modifier: '%c'", + *endptr); + } + break; + case 'v': + verbosity = optarg ? atoi(optarg) : 1; + break; + case 'l': + data_lines = atoi(optarg); + break; + case 'g': + if (optarg && strcmp(optarg, "-")) + plot_filename = optarg; + else + plot_fp = stdout; + break; + case 'H': + histogram_cells = atoi(optarg); + if (histogram_cells < 1 || histogram_cells > 1000) + error(1, EINVAL, "invalid number of histogram cells " + "(0 < cells <= 1000)"); + break; + case 'P': + max_prio = sched_get_priority_max(SCHED_FIFO); + responder_priority = atoi(optarg); + if (responder_priority < 0 || responder_priority > max_prio) + error(1, EINVAL, "invalid thread priority " + "(0 < priority < %d)", max_prio); + break; + case 'C': + force_cpu = true; + /* fallthrough */ + case 'c': + responder_cpu = atoi(optarg); + if (responder_cpu < 0 || responder_cpu >= CPU_SETSIZE) + error(1, EINVAL, "invalid CPU number"); + break; + case 'z': + case 'Z': + test_gpiolat = (c == 'z') + 1; + parse_host_spec(optarg, &gpio_monitor_ip); + break; + case 'I': + gpio_infd = parse_gpio_spec(optarg, &gpio_inpin, + &gpio_hdinflags, &gpio_evinflags); + break; + case 'O': + gpio_outfd = parse_gpio_spec(optarg, &gpio_outpin, + &gpio_hdoutflags, NULL); + break; + case '?': + default: + bad_usage(argc, argv); + return 1; + } + } + + if (optind < argc) { + bad_usage(argc, argv); + return 1; + } + + determine_responder_cpu(test_gpiolat == INBAND_GPIO_LAT); + + setlinebuf(stdout); + setlinebuf(stderr); + + if (!tuning && !timeout && !verbosity) { + fprintf(stderr, "--quiet requires --timeout, ignoring --quiet\n"); + verbosity = 1; + } + + if (background && verbosity) { + fprintf(stderr, "--background requires --quiet, taming verbosity down\n"); + verbosity = 0; + } + + if (tuning && (plot_filename || plot_fp)) { + fprintf(stderr, "--plot implies --measure, ignoring --plot\n"); + plot_filename = NULL; + plot_fp = NULL; + } + + if (background) { + signal(SIGHUP, SIG_IGN); + ret = daemon(0, 0); + if (ret) + error(1, errno, "cannot daemonize"); + } + + set_cpu_affinity(); + restrict_c_state(); + + sigaddset(&sigmask, SIGINT); + sigaddset(&sigmask, SIGTERM); + sigaddset(&sigmask, SIGHUP); + sigaddset(&sigmask, SIGALRM); + pthread_sigmask(SIG_BLOCK, &sigmask, NULL); + +#ifdef CONFIG_XENO_COBALT + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = sigdebug_handler; + sa.sa_flags = SA_SIGINFO | SA_RESTART; + sigaction(SIGDEBUG, &sa, NULL); +#endif + + spec = test_irqlat || test_klat || test_ulat || test_sirqlat || test_gpiolat; + if (!tuning) { + if (!spec) + test_ulat = 1; + else if (test_irqlat + test_klat + test_ulat + test_sirqlat + + (!!test_gpiolat) > 1) + error(1, EINVAL, "only one of -u, -k, -i, -s, -z or -Z " + "in measurement mode"); + } else { + /* Default to tune for all contexts. */ + if (!spec) + test_irqlat = test_klat = test_ulat = 1; + else if (test_sirqlat || test_gpiolat) + error(1, EINVAL, "-s/-z and -t are mutually exclusive"); + } + + if (!test_gpiolat) { + latmus_fd = open("/dev/rtdm/latmus", O_RDWR); + if (latmus_fd < 0) + error(1, errno, "cannot open latmus device"); + + if (reset) { + ret = ioctl(latmus_fd, RTTST_RTIOC_COBALT_LATIOC_RESET); + if (ret) + error(1, errno, "reset failed"); + } + } else { + if (gpio_infd < 0 || gpio_outfd < 0) + error(1, EINVAL, "-[zZ] require -I, -O for GPIO settings"); + if (test_gpiolat == OOB_GPIO_LAT) { + //gpio_hdinflags |= GPIOHANDLE_REQUEST_OOB; + //gpio_hdoutflags |= GPIOHANDLE_REQUEST_OOB; + } + } + + time(&start_time); + + if (!tuning) { + if (plot_filename) { + if (!access(plot_filename, F_OK)) + error(1, EINVAL, "declining to overwrite %s", + plot_filename); + plot_fp = fopen(plot_filename, "w"); + if (plot_fp == NULL) + error(1, errno, "cannot open %s for writing", + plot_filename); + } + type = test_irqlat ? COBALT_LAT_IRQ : test_klat ? + COBALT_LAT_KERN : test_sirqlat ? COBALT_LAT_SIRQ : + test_ulat ? COBALT_LAT_USER : + COBALT_LAT_LAST + test_gpiolat; + do_measurement(type); + } else { + if (verbosity) + printf("== latmus started for core tuning, " + "period=%d microseconds (may take a while)\n", + period_usecs); + + if (test_irqlat) + do_tuning(COBALT_LAT_IRQ); + + if (test_klat) + do_tuning(COBALT_LAT_KERN); + + if (test_ulat) + do_tuning(COBALT_LAT_USER); + + if (verbosity) + printf("== tuning completed after %ds\n", + (int)(time(NULL) - start_time)); + } + + return 0; +} -- 2.17.1