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:
parent
5353db0df9
commit
afb57502d3
|
@ -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, ¶m);
|
||||
|
||||
evl_attach_self("gpio-mon:%d", getpid());
|
||||
|
||||
return echo_device(device_name, line_in, handleflags_in,
|
||||
eventflags, loops, line_out, handleflags_out);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
* gpio-ping.patch: measures response time of benchmarks/gpio-echo
|
|
@ -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 = ð
|
||||
+ 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;
|
||||
+}
|
Loading…
Reference in New Issue