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, &param);
+       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, &param);
+       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,
+                               &timestamp);
+               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


Reply via email to