benchmarks: gpio-echo: external latency tester tool

This Linux-side tool is designed to echo the GPIO signals issued from
a remote board, which in turn measures the response time. The code has
been validated using a Linux-based rpi3b running gpio-echo, and a
Zephyr-based FRDMk64f board measuring the response time.

The current version of the Zephyr code is available in the zephyr/
subdirectory in patch format. This code is maintained at:
https://github.com/ldts/zephyr.git, branch evl-latency

For instance, GPIO23 can be used to receive test signals on rpi3b and
GPIO24 to respond to them.

gpio-echo is realtime capable (via EVL) or non-realtime capable
(standard Linux behaviour) - it depends on how you run it.

$ gpio-echo -n gpiochip0 -o 23 -t 24 -O -T -f

Once that process is started (and the necessary cabling is done) start
this Zephyr program, get the console and follow the instructions.

Connections:
-------------
 Zephyr - FRDMk64F:                Linux - rpi3b
  PIN 20 (PTE-24) ----------------  PIN 16 (GPIO 23)
  PIN 18 (PTE-25) ----------------  PIN 18 (GPIO 24)

Signed-off-by: Jorge Ramirez-Ortiz <jro@xenomai.org>
This commit is contained in:
Jorge Ramirez-Ortiz 2019-06-02 20:57:06 +02:00 committed by Philippe Gerum
parent 5353db0df9
commit afb57502d3
3 changed files with 1025 additions and 0 deletions

340
benchmarks/gpio-echo.c Normal file
View File

@ -0,0 +1,340 @@
/*
* SPDX-License-Identifier: MIT
*/
/*
* Usage:
* example to monitor GPIO 23 and respond on GPIO 24 using EVL to
* monitor and respond:
*
* $ gpio-echo -n gpiochip0 -o 23 -t 24 -O -T -f
*
* To be used in collaboration with an external test tool
* https://github.com/ldts/zephyr/commit/b157dc635196ce501e0cf2f70ae1bd5fe3f6a300
*
*/
#include <sys/types.h>
#include <time.h>
#include <stdbool.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <poll.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <evl/thread.h>
#include <evl/mutex.h>
#include <evl/clock.h>
#include <evl/sem.h>
#include <evl/proxy.h>
#include <linux/gpio.h>
#include <uapi/evl/devices/gpio.h>
#define VERBOSE 0
#if 0
#define gpio_printf(file, __fmt, __args...) fprintf(file, __fmt, ##__args)
#else
#define gpio_printf(file, __fmt, __args...) evl_printf(__fmt, ##__args)
#endif
static int gpio_tx_value(const int fd, struct gpiohandle_data *data, int oob)
{
int ret;
if (oob & GPIOHANDLE_REQUEST_OOB )
ret = oob_ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, data);
else
ret = ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, data);
if (ret == -1) {
ret = -errno;
gpio_printf(stderr, "Failed to issue %s (%d), %s\n",
"GPIOHANDLE_SET_LINE_VALUES_IOCTL", ret,
strerror(errno));
}
return ret;
}
static int echo_device(const char *device_name,
unsigned int line_in,
uint32_t handleflags_in,
uint32_t eventflags,
unsigned int loops,
unsigned int line_out,
uint32_t handleflags_out)
{
struct gpiohandle_request req_out = { 0 };
struct gpiohandle_data data_out = { 0 };
struct gpioevent_request req_in = { 0 };
struct gpiohandle_data data_in = { 0 };
char *chrdev_name;
int fd;
int ret;
int i = 0;
ret = asprintf(&chrdev_name, "/dev/%s", device_name);
if (ret < 0)
return -ENOMEM;
fd = open(chrdev_name, 0);
if (fd == -1) {
ret = -errno;
gpio_printf(stderr, "Failed to open %s\n", chrdev_name);
goto exit_close_error;
}
/* IN configuration */
req_in.handleflags = handleflags_in;
req_in.eventflags = eventflags;
req_in.lineoffset = line_in;
strcpy(req_in.consumer_label, "gpio-event-mon");
ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req_in);
if (ret == -1) {
ret = -errno;
gpio_printf(stderr, "Failed to issue GET EVENT "
"IOCTL (%d)\n",
ret);
goto exit_close_error;
}
/* Read initial states */
ret = ioctl(req_in.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data_in);
if (ret == -1) {
ret = -errno;
gpio_printf(stderr, "Failed to issue GPIOHANDLE GET LINE "
"VALUES IOCTL (%d)\n",
ret);
goto exit_close_error;
}
gpio_printf(stdout, "RX line %d on %s\n", line_in, device_name);
gpio_printf(stdout, " initial value: %d\n", data_in.values[0]);
if (handleflags_in & GPIOHANDLE_REQUEST_OOB)
gpio_printf(stdout, " out-of-band rx enabled\n");
/* OUT configuration */
req_out.lineoffsets[0] = line_out;
req_out.lines = 1;
req_out.flags = handleflags_out;
req_out.default_values[0] = 1;
strcpy(req_out.consumer_label, "gpio-event-echo");
ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req_out);
if (ret == -1) {
ret = -errno;
gpio_printf(stderr, "Failed to issue %s (%d), %s\n",
"GPIO_GET_LINEHANDLE_IOCTL", ret, strerror(errno));
gpio_printf(stderr, "Issue GPIO_GET_LINEHANDLE_IOCTL \n"
"req.lines %d, req.flags 0x%x req.lineoffsets[0] = %d, \n",
req_out.lines, req_out.flags, req_out.lineoffsets[0]);
goto exit_close_error;
}
gpio_printf(stdout, "TX line %d on %s\n", line_out, device_name);
gpio_printf(stdout, " initial value: %d\n", data_out.values[0]);
if (handleflags_out & GPIOHANDLE_REQUEST_OOB)
gpio_printf(stdout, " out-of-band tx enabled\n");
while (1) {
struct gpioevent_data event;
data_out.values[0] = 0;
ret = gpio_tx_value(req_out.fd, &data_out, handleflags_out);
if (ret == -1) {
ret = -errno;
gpio_printf(stderr, "Failed to issue %s (%d), %s\n",
"GPIO_GET_LINEHANDLE_IOCTL", ret, strerror(errno));
goto release_out_handler;
}
if (handleflags_in & GPIOHANDLE_REQUEST_OOB)
ret = oob_read(req_in.fd, &event, sizeof(event));
else
ret = read(req_in.fd, &event, sizeof(event));
if (ret == -1) {
if (errno == -EAGAIN) {
gpio_printf(stderr, "nothing available\n");
continue;
} else {
ret = -errno;
gpio_printf(stderr, "Failed to read event (%d)\n",
ret);
break;
}
}
if (ret != sizeof(event)) {
gpio_printf(stderr, "Reading event failed\n");
ret = -EIO;
break;
}
#if VERBOSE
gpio_printf(stdout, "\tGPIO EVENT[%04d] = %llu: ", i, event.timestamp);
#endif
switch (event.id) {
case GPIOEVENT_EVENT_RISING_EDGE:
#if VERBOSE
gpio_printf(stdout, "rising edge");
#endif
break;
case GPIOEVENT_EVENT_FALLING_EDGE:
#if VERBOSE
gpio_printf(stdout, "falling edge");
#endif
/* respond to sender with rising edge */
data_out.values[0] = 1;
ret = gpio_tx_value(req_out.fd, &data_out, handleflags_out);
if (ret == -1)
goto release_out_handler;
break;
default:
gpio_printf(stdout, "unknown event");
}
#if VERBOSE
gpio_printf(stdout, "\n");
#endif
i++;
if (i == loops)
break;
}
release_out_handler:
close(req_out.fd);
exit_close_error:
if (close(fd) == -1)
perror("Failed to close GPIO character device file");
free(chrdev_name);
return ret;
}
static void print_usage(void)
{
gpio_printf(stderr, "Usage: gpio-event-mon [options]...\n"
"Listen to events on GPIO lines, 0->1 1->0\n"
" -n <name> Listen on GPIOs on a named device (must be stated)\n"
" -o <n> Offset to monitor\n"
" -t <n> Offset to respond\n"
" -d Set line as open drain\n"
" -s Set line as open source\n"
" -O Enable out-of-band event handling\n"
" -T Enable out-of-band event tx handling\n"
" -r Listen for rising edges\n"
" -f Listen for falling edges\n"
" [-x <n>] pin process to CPU [=1]\n"
" [-c <n>] Do <n> loops (optional, infinite loop if not stated)\n"
" -? This helptext\n"
"\n"
"Example:\n"
"gpio-event-mon -n gpiochip0 -o 4 -r -f\n"
);
}
int main(int argc, char **argv)
{
const char *device_name = NULL;
unsigned int line_in = -1, line_out = -1;
unsigned int loops = 0;
uint32_t handleflags_in = GPIOHANDLE_REQUEST_INPUT;
uint32_t handleflags_out = GPIOHANDLE_REQUEST_OUTPUT;
uint32_t eventflags = 0;
int pin_cpu = -1;
struct sched_param param;
cpu_set_t cpu_set;
int c;
int ret;
while ((c = getopt(argc, argv, "c:n:o:t:x:dsOTrf?")) != -1) {
switch (c) {
case 'c':
loops = strtoul(optarg, NULL, 10);
break;
case 'n':
device_name = optarg;
break;
case 'o':
line_in = strtoul(optarg, NULL, 10);
break;
case 't':
line_out = strtoul(optarg, NULL, 10);
break;
case 'd':
handleflags_in |= GPIOHANDLE_REQUEST_OPEN_DRAIN;
break;
case 's':
handleflags_in |= GPIOHANDLE_REQUEST_OPEN_SOURCE;
break;
case 'O':
handleflags_in |= GPIOHANDLE_REQUEST_OOB;
break;
case 'T':
handleflags_out |= GPIOHANDLE_REQUEST_OOB;
break;
case 'r':
eventflags |= GPIOEVENT_REQUEST_RISING_EDGE;
break;
case 'f':
eventflags |= GPIOEVENT_REQUEST_FALLING_EDGE;
break;
case 'x':
pin_cpu = atoi(optarg);
if (pin_cpu < 1 || pin_cpu >= CPU_SETSIZE) {
printf("Invalid CPU number %d\n", pin_cpu);
print_usage();
return -1;
}
break;
case '?':
print_usage();
return -1;
}
}
if (!device_name || line_in == -1 || line_out == -1) {
print_usage();
return -1;
}
if (!eventflags) {
printf("No flags specified, listening on both rising and "
"falling edges\n");
eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
}
if (pin_cpu > 0) {
CPU_ZERO(&cpu_set);
CPU_SET(pin_cpu, &cpu_set);
ret = sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
if (ret)
error(1, errno, "cannot set affinity to CPU%d",
pin_cpu);
else
gpio_printf(stderr, "Affinity on CPU%d\n", pin_cpu);
} else
gpio_printf(stderr, "No cpu Affinity\n");
param.sched_priority = 98;
pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
evl_attach_self("gpio-mon:%d", getpid());
return echo_device(device_name, line_in, handleflags_in,
eventflags, loops, line_out, handleflags_out);
}

1
benchmarks/zephyr/README Normal file
View File

@ -0,0 +1 @@
* gpio-ping.patch: measures response time of benchmarks/gpio-echo

View File

@ -0,0 +1,684 @@
diff --git a/boards/arm/frdm_k64f/frdm_k64f.dts b/boards/arm/frdm_k64f/frdm_k64f.dts
index d1691d745e..8e9205e5d3 100644
--- a/boards/arm/frdm_k64f/frdm_k64f.dts
+++ b/boards/arm/frdm_k64f/frdm_k64f.dts
@@ -36,6 +36,8 @@
sw0 = &user_button_3;
sw1 = &user_button_2;
eth = &eth;
+ tx = &transmit_gpio;
+ rx = &receive_gpio;
};
chosen {
@@ -49,30 +51,46 @@
leds {
compatible = "gpio-leds";
+
red_led: led_0 {
gpios = <&gpiob 22 0>;
label = "User LD1";
};
+
green_led: led_1 {
gpios = <&gpioe 26 0>;
label = "User LD2";
};
+
blue_led: led_2 {
gpios = <&gpiob 21 0>;
label = "User LD3";
};
+
+ transmit_gpio: transmit {
+ gpios = <&gpioe 24 0>;
+ label = "To DUT";
+ };
};
gpio_keys {
compatible = "gpio-keys";
+
user_button_2: button_0 {
label = "User SW2";
gpios = <&gpioc 6 GPIO_INT_ACTIVE_LOW>;
};
+
user_button_3: button_1 {
label = "User SW3";
gpios = <&gpioa 4 GPIO_INT_ACTIVE_LOW>;
};
+
+ receive_gpio: receive {
+ label = "From DUT";
+ gpios = <&gpioe 25 GPIO_INT_ACTIVE_HIGH>;
+ };
+
};
};
diff --git a/boards/arm/frdm_k64f/pinmux.c b/boards/arm/frdm_k64f/pinmux.c
index 23bbc7bce0..0eaac97233 100644
--- a/boards/arm/frdm_k64f/pinmux.c
+++ b/boards/arm/frdm_k64f/pinmux.c
@@ -108,6 +108,9 @@ static int frdm_k64f_pinmux_init(struct device *dev)
| PORT_PCR_ODE_MASK);
pinmux_pin_set(porte, 25, PORT_PCR_MUX(kPORT_MuxAlt5)
| PORT_PCR_ODE_MASK);
+#else
+ pinmux_pin_set(porte, 24, PORT_PCR_MUX(kPORT_MuxAsGpio));
+ pinmux_pin_set(porte, 25, PORT_PCR_MUX(kPORT_MuxAsGpio));
#endif
#if CONFIG_ADC_1
diff --git a/samples/drivers/gpio_latency/CMakeLists.txt b/samples/drivers/gpio_latency/CMakeLists.txt
new file mode 100644
index 0000000000..95aabcc069
--- /dev/null
+++ b/samples/drivers/gpio_latency/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.13.1)
+include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
+project(gpio_latency)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/samples/drivers/gpio_latency/prj.conf b/samples/drivers/gpio_latency/prj.conf
new file mode 100644
index 0000000000..761150d3af
--- /dev/null
+++ b/samples/drivers/gpio_latency/prj.conf
@@ -0,0 +1,5 @@
+CONFIG_LOG=y
+CONFIG_GPIO=y
+CONFIG_TICKLESS_KERNEL=y
+CONFIG_CONSOLE_SUBSYS=y
+CONFIG_CONSOLE_GETLINE=y
diff --git a/samples/drivers/gpio_latency/sample.yaml b/samples/drivers/gpio_latency/sample.yaml
new file mode 100644
index 0000000000..bf0383f129
--- /dev/null
+++ b/samples/drivers/gpio_latency/sample.yaml
@@ -0,0 +1,11 @@
+sample:
+ name: GPIO Latency instrumentation tool
+tests:
+ sample.driver.gpio_latency:
+ tags: drivers
+ harness: console
+ harness_config:
+ type: one_line
+ regex:
+ - "Toggling (.*)"
+ depends_on: gpio
diff --git a/samples/drivers/gpio_latency/src/main.c b/samples/drivers/gpio_latency/src/main.c
new file mode 100644
index 0000000000..7e70971fbc
--- /dev/null
+++ b/samples/drivers/gpio_latency/src/main.c
@@ -0,0 +1,562 @@
+/*
+ * Copyright (c) 2019 EVL
+ * Author: Jorge Ramirez-Ortiz <jro@xenomai.org>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Usage:
+ * The DUT running Linux (ie on rpi3b) using GPIOS 23 (to receive) and GPIO24 (to
+ * send) would be running the gpio-echo code capable of responding to the
+ * requests sent by this latency measurement test code.
+ *
+ * gpio-echo is realtime capable (via EVL) or non-realtime capable (standard
+ * linux behviour) - it depends on how you build it.
+ *
+ * $ gpio-echo -n gpiochip0 -o 23 -t 24 -O -T -f
+ *
+ * Once that process is started and the necessary cabling is done start this
+ * Zephyr program, get the console * and follow the instructions
+ *
+ * Connections:
+ *
+ * Zephyr - FRDMk64F: Linux - rpi3b
+ * PIN 20 (PTE-24) ---------------- PIN 16 (GPIO 23)
+ * PIN 18 (PTE-25) ---------------- PIN 18 (GPIO 24)
+ *
+ */
+
+#include <atomic.h>
+#include <zephyr.h>
+#include <misc/printk.h>
+#include <device.h>
+#include <gpio.h>
+#include <misc/util.h>
+#include <console.h>
+#include <logging/log.h>
+
+#define ARRAY_LEN(x) (sizeof(x)/sizeof(x[0]))
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
+#define LOG_MODULE_NAME latency_tester
+#define LOG_LEVEL LOG_LEVEL_INF
+LOG_MODULE_REGISTER();
+
+#define GPIO_OUT0_DRV_NAME LED0_GPIO_CONTROLLER
+#define GPIO_OUT0_PIN LED0_GPIO_PIN
+
+#define GPIO_OUT1_DRV_NAME TX_GPIO_CONTROLLER
+#define GPIO_OUT1_PIN TX_GPIO_PIN
+
+#define GPIO_IN_DRV_NAME RX_GPIO_CONTROLLER
+#define GPIO_INT_PIN RX_GPIO_PIN
+
+#define LOOPBACK 0 /* enable loopback for calibration */
+#define CALIBRATED_LATENCY_NS 1000 /* FRDMk64F calibration */
+#define STAMP_SEED 0xFFFFFFFF
+#define MAX_SAMPLES 20000
+#define HISTOGRAM_BINS 100
+#define FIXED_HISTOGRAM_BINS 20
+
+static atomic_t stamp = ATOMIC_INIT(STAMP_SEED);
+static struct device *gpio_out1_dev;
+static u64_t calibrated_latency;
+static u64_t samples[MAX_SAMPLES];
+static u32_t cnt;
+
+/* avoid stack overflow */
+static struct {
+ u64_t val;
+ int nbr;
+} bins[HISTOGRAM_BINS];
+
+static struct {
+ u64_t val;
+ int nbr;
+} fixed_bins[FIXED_HISTOGRAM_BINS];
+
+void gpio_callback(struct device *port, struct gpio_callback *cb, u32_t pins)
+{
+ u64_t delta = SYS_CLOCK_HW_CYCLES_TO_NS64(k_cycle_get_32() - stamp);
+ int ret;
+
+ if (atomic_get(&stamp) == STAMP_SEED)
+ return;
+
+#if LOOPBACK == 1
+ /* deassert signal to DUT */
+ ret = gpio_pin_write(gpio_out1_dev, GPIO_OUT1_PIN, 1);
+ if (ret) {
+ LOG_ERR("gpio out1 error: clear");
+ return;
+ }
+#endif
+ atomic_set(&stamp, STAMP_SEED);
+
+ if (cnt < ARRAY_LEN(samples)) {
+ samples[cnt] = delta - calibrated_latency;
+ cnt = cnt + 1;
+ }
+}
+
+void main(void)
+{
+ static struct gpio_callback gpio_cb;
+ struct device *gpio_out0_dev;
+ struct device *gpio_in_dev;
+ struct test_config {
+ int loop_for_ever;
+ s64_t loop_for_ever_start;
+ u64_t max_latency;
+ int max_latency_on;
+ int duration;
+ int period;
+ } config = {
+ .max_latency = 0,
+ .max_latency_on = 0,
+ .duration = 0,
+ .period = 0,
+ .loop_for_ever = 0,
+ };
+
+ s64_t reftime;
+ int nsamples, min_bin_size, bin_size, max_bin_len, fixed_max_bin_len;
+ int ret, i, j, min_i, max_i;
+ u64_t min, max = 0;
+ int led_on = 1, led_period;
+ char *val;
+ u64_t max_latency, min_latency;
+ u64_t runs = 0;
+ int fixed_bin_overflows;
+
+ console_getline_init();
+
+ gpio_out0_dev = device_get_binding(GPIO_OUT0_DRV_NAME);
+ if (!gpio_out0_dev) {
+ printk("cannot find %s\n", GPIO_OUT0_DRV_NAME);
+ return;
+ }
+
+ gpio_out1_dev = device_get_binding(GPIO_OUT1_DRV_NAME);
+ if (!gpio_out1_dev) {
+ printk("cannot find %s\n", GPIO_OUT1_DRV_NAME);
+ return;
+ }
+
+ gpio_in_dev = device_get_binding(GPIO_IN_DRV_NAME);
+ if (!gpio_in_dev) {
+ printk("cannot find %s\n", GPIO_IN_DRV_NAME);
+ return;
+ }
+
+ /* configured for FRDM_k64F LED */
+ ret = gpio_pin_configure(gpio_out0_dev, GPIO_OUT0_PIN, (GPIO_DIR_OUT));
+ if (ret) {
+ printk("error configuring pin %d\n", GPIO_OUT0_PIN);
+ return;
+ }
+
+ /* configured for FRDM_k64F signal out */
+ ret = gpio_pin_configure(gpio_out1_dev, GPIO_OUT1_PIN, (GPIO_DIR_OUT));
+ if (ret) {
+ printk("error configuring pin %d\n", GPIO_OUT1_PIN);
+ return;
+ }
+
+ ret = gpio_pin_write(gpio_out1_dev, GPIO_OUT1_PIN, 1);
+ if (ret) {
+ LOG_ERR("gpio out error: set");
+ return;
+ }
+
+ /* Setup GPIO input, and triggers on level low */
+ ret = gpio_pin_configure(gpio_in_dev, GPIO_INT_PIN,
+ (GPIO_DIR_IN | GPIO_INT |
+ GPIO_INT_EDGE | GPIO_INT_ACTIVE_LOW));
+ if (ret) {
+ printk("error configuring pin %d\n", GPIO_INT_PIN);
+ return;
+ }
+
+ gpio_init_callback(&gpio_cb, gpio_callback, BIT(GPIO_INT_PIN));
+
+ ret = gpio_add_callback(gpio_in_dev, &gpio_cb);
+ if (ret) {
+ printk("cannot setup callback\n");
+ return;
+ }
+
+ ret = gpio_pin_enable_callback(gpio_in_dev, GPIO_INT_PIN);
+ if (ret) {
+ printk("error enabling callback\n");
+ return;
+ }
+again:
+ printk("\n=======================\n");
+ printk("Interrupt Latency tester\n");
+ printk("Max samples: %d\n", ARRAY_LEN(samples));
+ printk("=========================\n");
+
+ printk("Select test (default t)\n");
+ printk(" * : run until latency hit\n");
+ printk(" ~ : run for ever\n");
+ printk(" t : time bounded\n");
+ printk(" : ");
+ max_latency = 0;
+ min_latency = 2000000;
+
+ config.max_latency_on = 0;
+ config.loop_for_ever = 0;
+ config.loop_for_ever_start = 0;
+ config.max_latency = 0;
+ runs = 0;
+ val = console_getline();
+ if (*val == '*') {
+ printk("Max latency that will stop the test [usec, default 80] : ");
+ val = console_getline();
+ config.max_latency = 1000 * atoi(val);
+ if (!config.max_latency)
+ config.max_latency = 80 * 1000;
+
+ /* make it small so user wont have to wait when threshold hit */
+ config.duration = 1;
+ } else if (*val == '~') {
+ config.loop_for_ever = 1;
+ config.loop_for_ever_start = k_uptime_get();
+ config.duration = 1;
+
+ for (i =0; i < ARRAY_LEN(fixed_bins); i++) {
+ fixed_bins[i].val = i * 10 * 1000; /* nsecs */
+ fixed_bins[i].nbr = 0;
+ }
+
+ } else {
+ printk("Enter test duration in seconds (default 10) :");
+ val = console_getline();
+ config.duration = atoi(val);
+ if (!config.duration)
+ config.duration = 10;
+ }
+
+ printk("\n");
+
+ if (config.max_latency || config.loop_for_ever) {
+ /* the lowest reasonable value for a 1 second test loop */
+ printk("Enter period [default 1 msec]: ");
+ } else
+ printk("Enter period [default 5 msec]: ");
+
+ val = console_getline();
+ config.period = atoi(val) * 1000;
+ if (!config.period)
+ config.period = (config.max_latency || config.loop_for_ever) ? 1000 : 5000;
+
+ printk("Enter calibrated latency [default %d nsec]: ",
+ CALIBRATED_LATENCY_NS);
+ val = console_getline();
+ calibrated_latency = atoi(val);
+ if (!calibrated_latency)
+ calibrated_latency = CALIBRATED_LATENCY_NS;
+
+ if (config.max_latency)
+ printk(" - test duration (1 sec) until latency hits %lld\n",
+ config.max_latency);
+ else if (config.loop_for_ever)
+ printk(" - test duration unbounded\n");
+ else
+ printk(" - test duration %d sec\n" ,config.duration);
+
+ printk(" - period %d usec\n"
+ " - calibrated latency %lld ns\n",
+ config.period, calibrated_latency);
+
+ nsamples = config.duration * 1000000 / config.period;
+ if (nsamples > ARRAY_LEN(samples)) {
+ nsamples = ARRAY_LEN(samples);
+ printk("\nWARNING: not enough space, test will finish in %d secs!!\n",
+ ARRAY_LEN(samples) * config.period / 1000000);
+ }
+
+ if (!config.max_latency_on && !config.loop_for_ever)
+ printk("\nTest will capture %d samples\n", nsamples);
+
+ printk("Press any key to begin ");
+ val = console_getline();
+ if (config.max_latency)
+ config.max_latency_on = 1;
+
+ /* work on milliseconds */
+ config.duration = config.duration * 1000;
+ fixed_max_bin_len = 0;
+ fixed_bin_overflows = 0;
+run:
+ runs++;
+ /* always clear the array and the histogram data */
+ memset(samples, 0, sizeof(samples));
+ for (i =0; i < ARRAY_LEN(bins); i++) {
+ bins[i].val = 0;
+ bins[i].nbr = 0;
+ }
+ led_period = 200000;
+ cnt = 0;
+ reftime = k_uptime_get();
+ while ((k_uptime_get() - reftime < config.duration) &&
+ (cnt < ARRAY_LEN(samples))) {
+
+ if (atomic_get(&stamp) != STAMP_SEED) {
+ if (config.loop_for_ever) {
+ s64_t now = k_uptime_get() / 1000;
+ s64_t secs = now - config.loop_for_ever_start / 1000;
+ s64_t hours, minutes;
+ hours = secs / 3600;
+ secs = secs - hours * 3600;
+ minutes = secs / 60;
+ secs = secs - minutes * 60;
+
+ printk("\nLatency test:\n");
+ printk(" nsamples : %lld\n", (runs - 1) * nsamples + cnt);
+ printk(" max latency : %lld nsec\n", max_latency);
+ printk(" min latency : %lld nsec\n", min_latency);
+ printk(" overflows : %04d\n", fixed_bin_overflows);
+ printk(" duration : %04lld:%02lld:%02lld\n",
+ hours, minutes, secs);
+ printk("------- latency --------- -- nbr -- ---------------- chart ----------------------\n");
+ for (i = 0; i < ARRAY_LEN(fixed_bins); i++) {
+ char bar[50] = { '\0' };
+ int k;
+
+ for (j = 0; j < FIXED_HISTOGRAM_BINS; j++) {
+ if (fixed_bins[i].nbr < (j * fixed_max_bin_len / 50))
+ break;
+ }
+
+ for (k = 0; k < j - 1; k++ ) {
+ bar[k] = '-';
+ }
+
+ bar[j - 1] = '|';
+
+ printk(" [%07lld - %07lld ns] %04d %s\n",
+ i ? fixed_bins[i - 1].val : 0,
+ fixed_bins[i].val, fixed_bins[i].nbr, fixed_bins[i].nbr ? bar : "");
+ }
+
+ do {
+ char bar[50] = { '\0' };
+ int k;
+
+ for (j = 0; j < FIXED_HISTOGRAM_BINS; j++) {
+ if (fixed_bin_overflows < (j * fixed_max_bin_len / 50))
+ break;
+ }
+
+ for (k = 0; k < j - 1; k++ ) {
+ bar[k] = '-';
+ }
+
+ bar[k] = '|';
+
+ printk(" [%07lld - max] %04d %s\n",
+ fixed_bins[i - 1].val, fixed_bin_overflows,
+ fixed_bin_overflows > 0 ? bar : "");
+ } while (0);
+ } else
+ printk("Deadline missed, increase period\n\n");
+
+ atomic_set(&stamp, STAMP_SEED);
+ goto again;
+ }
+
+ /* start waiting for notifications */
+ atomic_set(&stamp, k_cycle_get_32());
+
+ /* send the signal */
+ ret = gpio_pin_write(gpio_out1_dev, GPIO_OUT1_PIN, 0);
+ if (ret) {
+ printk("gpio out error: set");
+ break;
+ }
+
+ /* toggle the LED */
+ ret = gpio_pin_write(gpio_out0_dev, GPIO_OUT0_PIN, led_on);
+ if (ret) {
+ printk("gpio out error: set");
+ break;
+ }
+
+#if LOOPBACK == 0
+ /* deassert signal to DUT */
+ k_busy_wait(1);
+ ret = gpio_pin_write(gpio_out1_dev, GPIO_OUT1_PIN, 1);
+ if (ret) {
+ printk("gpio out1 error: clear");
+ return;
+ }
+#endif
+ k_busy_wait(config.period);
+ led_period -= config.period;
+ if (led_period <= 0) {
+ led_period = 200000;
+ led_on ^= 1;
+ }
+ }
+
+ /* make sure the caches are hot */
+ if (runs < 2)
+ goto run;
+
+ min = max = min_i = max_i = 0;
+ for (i = 0; i < cnt; i++) {
+ if (min == 0 || min > samples[i]) {
+ min = samples[i];
+ min_i = i;
+ }
+
+ if (max < samples[i]) {
+ max = samples[i];
+ max_i = i;
+ }
+ }
+
+ /* check if max latency was hit */
+ if (config.max_latency_on) {
+ if (max > max_latency) {
+ max_latency = max;
+ /* some feedback in the loop for ever case */
+ printk(" max latency update : %lld nsec\n", max_latency);
+ }
+
+ if (max < config.max_latency)
+ goto run;
+ else
+ config.max_latency_on = 0;
+ }
+
+ if (config.loop_for_ever) {
+ if (max > max_latency) {
+ s64_t now = k_uptime_get() / 1000;
+ s64_t secs = now - config.loop_for_ever_start / 1000;
+ s64_t hours, minutes;
+
+ max_latency = max;
+ hours = secs / 3600;
+ secs = secs - hours * 3600;
+ minutes = secs / 60;
+ secs = secs - minutes * 60;
+ /* some feedback in the loop for ever case */
+ printk(" max latency hit : %08lld nsec, samples: %08lld time: %04lld:%02lld:%02lld\n",
+ max_latency, (runs - 1) * nsamples,
+ hours, minutes, secs);
+ }
+ if (min < min_latency)
+ min_latency = min;
+
+ for (i = 0; i < cnt; i++) {
+ for (j =0; j < ARRAY_LEN(fixed_bins); j++) {
+ if (samples[i] <= fixed_bins[j].val) {
+ fixed_bins[j].nbr++;
+ if (fixed_bins[j].nbr > fixed_max_bin_len)
+ fixed_max_bin_len = fixed_bins[j].nbr;
+ break;
+ }
+ }
+
+ if (j >= ARRAY_LEN(fixed_bins) )
+ fixed_bin_overflows += 1;
+ }
+
+ goto run;
+ }
+
+ /* make sure we have enough bins for the histogram (non RT DUT) */
+ min_bin_size = ((max - min) / ARRAY_LEN(bins)) / 1000;
+ if (min_bin_size < 1)
+ min_bin_size = 1;
+
+ printk("Histogram bin size in usec [min %d, default %d]: ",
+ min_bin_size, 5 * min_bin_size);
+ val = console_getline();
+ bin_size = 1000 * atoi(val);
+ if (!bin_size || bin_size < min_bin_size * 1000)
+ bin_size = 5 * min_bin_size * 1000;
+
+ for (i = 0; i < ARRAY_LEN(bins); i++) {
+ bins[i].val = min + bin_size * i;
+ if (bins[i].val > max) {
+ bins[i].val = max;
+ break;
+ }
+ }
+
+ max_bin_len = 0;
+ for (i = 0; i < cnt; i++) {
+ for (j =0; j < ARRAY_LEN(bins); j++) {
+ if (samples[i] <= bins[j].val) {
+ bins[j].nbr++;
+ if (bins[j].nbr > max_bin_len)
+ max_bin_len = bins[j].nbr;
+ break;
+ }
+ }
+ }
+
+ printk("\nTotal number of samples: %d\n", cnt);
+ printk("------- latency -------- -- nbr -- ---------------- chart ----------------------\n");
+ for (i = 0; i < ARRAY_LEN(bins); i++) {
+ char bar[50] = { '\0' };
+ int k;
+
+ if (bins[i].val == 0)
+ break;
+
+ for (j = 0; j < 50; j++) {
+ if (bins[i].nbr < (j * max_bin_len / 50))
+ break;
+ }
+
+ for (k = 0; k < j - 1; k++ ) {
+ bar[k] = '-';
+ }
+
+ bar[j - 1] = '|';
+
+ printk(" [%07lld - %07lld ns] %04d %s\n",
+ i ? bins[i - 1].val : 0,
+ bins[i].val, bins[i].nbr, bins[i].nbr ? bar : "");
+ }
+
+ printk("\nLatency limits:\n");
+ printk("-------------------------\n");
+ printk(" min[%05d] = %07lld nsec at %05d msec\n",
+ min_i, min, min_i * config.period / 1000);
+ printk(" max[%05d] = %07lld nsec at %05d msec\n",
+ max_i, max, max_i * config.period / 1000);
+ printk(" test duration: %d msec\n", config.duration);
+ printk("\n");
+
+ printk("View the the timeline [y/N]: ");
+ val = console_getline();
+ if (*val != 'y' && *val != 'Y')
+ goto again;
+
+ printk("--- time --- -- value-- ------------------ chart -------------------------\n");
+ for (i = 0; i < cnt; i++) {
+ char bar[50] = { '\0' };
+ int k;
+
+ for (j = 0; j < 50; j++) {
+ if (samples[i] < (j * max / 50))
+ break;
+ }
+
+ for (k = 0; k < j; k++ ) {
+ bar[k] = '.';
+ }
+
+ printk("[%06d msec] %06lld %s\n",
+ i * config.period / 1000, samples[i], bar);
+ }
+
+ printk("\n\n");
+
+ goto again;
+}