Subject: Introduce IRQ latency benchmark This patch introduces another rttesting driver, xeno_irqbench, for measuring external IRQ latencies. The irqbench device is controlled by a user-mode tool irqloop. A second tool for plain Linux, irqbench, is provided to trigger the event over serial or parallel cross-link (the latter is incomplete yet) and measure the reaction latency. --- configure.in | 2 doc/txt/Makefile.am | 1 doc/txt/irqbench.txt | 51 ++++ include/rtdm/rttesting.h | 38 ++ ksrc/drivers/Makefile | 2 ksrc/drivers/testing/Kconfig | 21 + ksrc/drivers/testing/Makefile | 13 - ksrc/drivers/testing/irqbench.c | 470 +++++++++++++++++++++++++++++++++++++ src/testsuite/Makefile.am | 2 src/testsuite/irqbench/Makefile.am | 47 +++ src/testsuite/irqbench/irqbench.c | 306 ++++++++++++++++++++++++ src/testsuite/irqbench/irqloop.c | 173 +++++++++++++ src/testsuite/irqbench/runinfo | 1 13 files changed, 1116 insertions(+), 11 deletions(-) Index: xenomai/include/rtdm/rttesting.h =================================================================== --- xenomai.orig/include/rtdm/rttesting.h +++ xenomai/include/rtdm/rttesting.h @@ -94,6 +94,28 @@ typedef struct rttst_tmbench_config { } rttst_tmbench_config_t; +#define RTTST_IRQBENCH_USER_TASK 0 +#define RTTST_IRQBENCH_KERNEL_TASK 1 +#define RTTST_IRQBENCH_HANDLER 2 + +#define RTTST_IRQBENCH_SERPORT 0 +#define RTTST_IRQBENCH_PARPORT 1 + +struct rttst_irqbench_config { + int mode; + int priority; + int calibration_loops; + unsigned int port_type; + unsigned long port_ioaddr; + unsigned int port_irq; +} rttst_irqbench_config_t; + +struct rttst_irqbench_stats { + unsigned long long irqs_received; + unsigned long long irqs_acknowledged; +} rttst_irqbench_stats_t; + + #define RTTST_SWTEST_FPU 0x1 #define RTTST_SWTEST_USE_FPU 0x2 /* Only for kernel-space tasks. */ @@ -134,6 +156,22 @@ struct rttst_swtest_dir { _IOWR(RTIOC_TYPE_TESTING, 0x11, struct rttst_overall_bench_res) +#define RTTST_RTIOC_IRQBENCH_START \ + _IOW(RTIOC_TYPE_TESTING, 0x20, struct rttst_irqbench_config) + +#define RTTST_RTIOC_IRQBENCH_STOP \ + _IO(RTIOC_TYPE_TESTING, 0x21) + +#define RTTST_RTIOC_IRQBENCH_GET_STATS \ + _IOR(RTIOC_TYPE_TESTING, 0x22, struct rttst_irqbench_stats) + +#define RTTST_RTIOC_IRQBENCH_WAIT_IRQ \ + _IO(RTIOC_TYPE_TESTING, 0x23) + +#define RTTST_RTIOC_IRQBENCH_REPLY_IRQ \ + _IO(RTIOC_TYPE_TESTING, 0x24) + + #define RTTST_RTIOC_SWTEST_SET_TASKS_COUNT \ _IOW(RTIOC_TYPE_TESTING, 0x30, unsigned long) Index: xenomai/configure.in =================================================================== --- xenomai.orig/configure.in +++ xenomai/configure.in @@ -82,6 +82,7 @@ case "$host" in esac AC_MSG_RESULT([$XENO_TARGET_ARCH]) +AM_CONDITIONAL(XENO_TARGET_ARCH_I386,[test $XENO_TARGET_ARCH = i386]) dnl dnl Parse options @@ -602,6 +603,7 @@ AC_CONFIG_FILES([ \ src/testsuite/switchbench/Makefile \ src/testsuite/cyclic/Makefile \ src/testsuite/switchtest/Makefile \ + src/testsuite/irqbench/Makefile \ include/Makefile \ include/asm-generic/Makefile \ include/asm-blackfin/Makefile \ Index: xenomai/ksrc/drivers/Makefile =================================================================== --- xenomai.orig/ksrc/drivers/Makefile +++ xenomai/ksrc/drivers/Makefile @@ -11,7 +11,7 @@ else subdir-$(CONFIG_XENO_DRIVERS_16550A) += 16550A subdir-$(CONFIG_XENO_DRIVERS_TIMERBENCH) += testing - +subdir-$(CONFIG_XENO_DRIVERS_IRQBENCH) += testing subdir-$(CONFIG_XENO_DRIVERS_SWITCHTEST) += testing include $(TOPDIR)/Rules.make Index: xenomai/ksrc/drivers/testing/Kconfig =================================================================== --- xenomai.orig/ksrc/drivers/testing/Kconfig +++ xenomai/ksrc/drivers/testing/Kconfig @@ -6,10 +6,19 @@ config XENO_DRIVERS_TIMERBENCH Kernel-based benchmark driver for timer latency evaluation. See testsuite/latency for a possible front-end. +config XENO_DRIVERS_IRQBENCH + depends on XENO_SKIN_RTDM + tristate "IRQ benchmark driver" + default n + help + Loopback driver for IRQ latency evaluation over serial or parallel + port links. Additionally requires user-space helper and a logging tool + (see testsuite/irqbench). + config XENO_DRIVERS_SWITCHTEST - depends on XENO_SKIN_RTDM - tristate "Context switch unit testing driver" - default n - help - Kernel-based driver for unit testing context switches and - FPU switches. + depends on XENO_SKIN_RTDM + tristate "Context switch unit testing driver" + default n + help + Kernel-based driver for unit testing context switches and + FPU switches. Index: xenomai/ksrc/drivers/testing/Makefile =================================================================== --- xenomai.orig/ksrc/drivers/testing/Makefile +++ xenomai/ksrc/drivers/testing/Makefile @@ -5,11 +5,13 @@ ifeq ($(PATCHLEVEL),6) EXTRA_CFLAGS += -Iinclude/xenomai obj-$(CONFIG_XENO_DRIVERS_TIMERBENCH) += xeno_timerbench.o - +obj-$(CONFIG_XENO_DRIVERS_IRQBENCH) += xeno_irqbench.o obj-$(CONFIG_XENO_DRIVERS_SWITCHTEST) += xeno_switchtest.o xeno_timerbench-y := timerbench.o +xeno_irqbench-y := irqbench.o + xeno_switchtest-y := switchtest.o EXTRA_CFLAGS += -Iinclude/xenomai @@ -21,14 +23,16 @@ else O_TARGET := built-in.o obj-$(CONFIG_XENO_DRIVERS_TIMERBENCH) += xeno_timerbench.o - +obj-$(CONFIG_XENO_DRIVERS_IRQBENCH) += xeno_irqbench.o obj-$(CONFIG_XENO_DRIVERS_SWITCHTEST) += xeno_switchtest.o xeno_timerbench-objs := timerbench.o +xeno_irqbench-objs := irqbench.o + xeno_switchtest-objs := switchtest.o -export-objs := $(xeno_timerbench-objs) $(xeno_switchtest-objs) +export-objs := $(xeno_timerbench-objs) $(xeno_irqbench-objs) $(xeno_switchtest-objs) EXTRA_CFLAGS += -I$(TOPDIR)/include/xenomai -I$(TOPDIR)/include/xenomai/compat @@ -37,6 +41,9 @@ include $(TOPDIR)/Rules.make xeno_timerbench.o: $(xeno_timerbench-objs) $(LD) -r -o $@ $(xeno_timerbench-objs) +xeno_irqbench.o: $(xeno_irqbench-objs) + $(LD) -r -o $@ $(xeno_irqbench-objs) + xeno_switchtest.o: $(xeno_switchtest-objs) $(LD) -r -o $@ $(xeno_switchtest-objs) Index: xenomai/ksrc/drivers/testing/irqbench.c =================================================================== --- /dev/null +++ xenomai/ksrc/drivers/testing/irqbench.c @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2006 Jan Kiszka . + * + * Xenomai is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Xenomai is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xenomai; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include +#include +#include + +/* --- Serial port --- */ + +#define MSR_DCTS 0x01 +#define MSR_DDSR 0x02 +#define MSR_DDCD 0x08 + +#define MCR_RTS 0x02 +#define MCR_OUT2 0x08 + +#define IER_MODEM 0x08 + +#define RHR(ctx) (ctx->port_ioaddr + 0) /* Receive Holding Buffer */ +#define IER(ctx) (ctx->port_ioaddr + 1) /* Interrupt Enable Register */ +#define IIR(ctx) (ctx->port_ioaddr + 2) /* Interrupt Id Register */ +#define LCR(ctx) (ctx->port_ioaddr + 3) /* Line Control Register */ +#define MCR(ctx) (ctx->port_ioaddr + 4) /* Modem Control Register */ +#define LSR(ctx) (ctx->port_ioaddr + 5) /* Line Status Register */ +#define MSR(ctx) (ctx->port_ioaddr + 6) /* Modem Status Register */ + +/* --- Parallel port --- */ + +#define CTRL_INIT 0x04 + +#define STAT_STROBE 0x10 + +#define DATA(ctx) (ctx->port_ioaddr + 0) /* Data register */ +#define STAT(ctx) (ctx->port_ioaddr + 1) /* Status register */ +#define CTRL(ctx) (ctx->port_ioaddr + 2) /* Control register */ + +struct rt_irqbench_context { + int mode; + int port_type; + unsigned long port_ioaddr; + unsigned int toggle; + struct rttst_irqbench_stats stats; + rtdm_irq_t irq_handle; + rtdm_event_t irq_event; + rtdm_task_t irq_task; + struct semaphore nrt_mutex; +}; + +static unsigned int start_index; + +module_param(start_index, uint, 0400); +MODULE_PARM_DESC(start_index, "First device instance number to be used"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("jan.kiszka@domain.hid"); + + +static inline int rt_irqbench_check_irq(struct rt_irqbench_context *ctx) +{ + int status; + + + switch (ctx->port_type) { + case RTTST_IRQBENCH_SERPORT: + status = inb(MSR(ctx)); + if (status & (MSR_DDSR | MSR_DDCD)) + xntrace_user_freeze(0, 0); + if (!(status & MSR_DCTS)) + return 0; + break; + + case RTTST_IRQBENCH_PARPORT: + // todo + break; + } + ctx->stats.irqs_received++; + return 1; +} + + +static inline void rt_irqbench_hwreply(struct rt_irqbench_context *ctx) +{ + switch (ctx->port_type) { + case RTTST_IRQBENCH_SERPORT: + /* toggle RTS */ + ctx->toggle ^= MCR_RTS; + outb(ctx->toggle, MCR(ctx)); + break; + + case RTTST_IRQBENCH_PARPORT: + ctx->toggle ^= 0xFF; + outb(ctx->toggle, DATA(ctx)); + break; + } + xntrace_special(0xBE, 0); + ctx->stats.irqs_acknowledged++; +} + + +static void rt_irqbench_task(void *arg) +{ + struct rt_irqbench_context *ctx = (struct rt_irqbench_context *)arg; + + + while (1) { + if (rtdm_event_wait(&ctx->irq_event) < 0) + return; + rt_irqbench_hwreply(ctx); + } +} + + +static int rt_irqbench_task_irq(rtdm_irq_t *irq_handle) +{ + struct rt_irqbench_context *ctx; + + + ctx = rtdm_irq_get_arg(irq_handle, struct rt_irqbench_context); + + if (rt_irqbench_check_irq(ctx)) + rtdm_event_signal(&ctx->irq_event); + + return RTDM_IRQ_HANDLED; +} + + +static int rt_irqbench_direct_irq(rtdm_irq_t *irq_handle) +{ + struct rt_irqbench_context *ctx; + + + ctx = rtdm_irq_get_arg(irq_handle, struct rt_irqbench_context); + + if (rt_irqbench_check_irq(ctx)) + rt_irqbench_hwreply(ctx); + + return RTDM_IRQ_HANDLED; +} + + +static int rt_irqbench_stop(struct rt_irqbench_context *ctx) +{ + if (ctx->mode < 0) + return -EINVAL; + + /* Disable hardware */ + switch (ctx->port_type) { + case RTTST_IRQBENCH_SERPORT: + outb(0, IER(ctx)); + break; + + case RTTST_IRQBENCH_PARPORT: + outb(0, CTRL(ctx)); + break; + } + + rtdm_irq_free(&ctx->irq_handle); + + if (ctx->mode == RTTST_IRQBENCH_KERNEL_TASK) + rtdm_task_destroy(&ctx->irq_task); + + ctx->mode = -1; + + return 0; +} + + +static int rt_irqbench_open(struct rtdm_dev_context *context, + rtdm_user_info_t *user_info, int oflags) +{ + struct rt_irqbench_context *ctx; + + + ctx = (struct rt_irqbench_context *)context->dev_private; + + ctx->mode = -1; + rtdm_event_init(&ctx->irq_event, 0); + init_MUTEX(&ctx->nrt_mutex); + + return 0; +} + + +static int rt_irqbench_close(struct rtdm_dev_context *context, + rtdm_user_info_t *user_info) +{ + struct rt_irqbench_context *ctx; + + + ctx = (struct rt_irqbench_context *)context->dev_private; + + down(&ctx->nrt_mutex); + rt_irqbench_stop(ctx); + rtdm_event_destroy(&ctx->irq_event); + up(&ctx->nrt_mutex); + + return 0; +} + + +static int rt_irqbench_ioctl_nrt(struct rtdm_dev_context *context, + rtdm_user_info_t *user_info, int request, + void *arg) +{ + struct rt_irqbench_context *ctx; + int ret = 0; + + + ctx = (struct rt_irqbench_context *)context->dev_private; + + switch (request) { + case RTTST_RTIOC_IRQBENCH_START: { + struct rttst_irqbench_config config_buf; + struct rttst_irqbench_config *config; + + config = (struct rttst_irqbench_config *)arg; + if (user_info) { + if (!rtdm_read_user_ok(user_info, arg, + sizeof(struct rttst_irqbench_config)) || + rtdm_copy_from_user(user_info, &config_buf, arg, + sizeof(struct rttst_irqbench_config))) + return -EFAULT; + + config = &config_buf; + } + + if (config->port_type > RTTST_IRQBENCH_PARPORT) + return -EINVAL; + + down(&ctx->nrt_mutex); + + if (test_bit(RTDM_CLOSING, &context->context_flags)) + goto unlock_start_out; + + ctx->port_type = config->port_type; + ctx->port_ioaddr = config->port_ioaddr; + + /* Initialise hardware */ + switch (ctx->port_type) { + case RTTST_IRQBENCH_SERPORT: + ctx->toggle = MCR_OUT2; + + /* Reset DLAB, reset RTS, enable OUT2 */ + outb(0, LCR(ctx)); + outb(MCR_OUT2, MCR(ctx)); + + /* Mask all UART interrupts and clear pending ones. */ + outb(0, IER(ctx)); + inb(IIR(ctx)); + inb(LSR(ctx)); + inb(RHR(ctx)); + inb(MSR(ctx)); + break; + + case RTTST_IRQBENCH_PARPORT: + ctx->toggle = 0xAA; + outb(0xAA, DATA(ctx)); + outb(CTRL_INIT, CTRL(ctx)); + break; + } + + switch (config->mode) { + case RTTST_IRQBENCH_USER_TASK: + ret = rtdm_irq_request(&ctx->irq_handle, config->port_irq, + rt_irqbench_task_irq, 0, + "irqbench", ctx); + break; + + case RTTST_IRQBENCH_KERNEL_TASK: + ret = rtdm_irq_request(&ctx->irq_handle, config->port_irq, + rt_irqbench_task_irq, 0, + "irqbench", ctx); + if (ret < 0) + goto unlock_start_out; + + ret = rtdm_task_init(&ctx->irq_task, "irqbench", + rt_irqbench_task, ctx, + config->priority, 0); + if (ret < 0) + rtdm_irq_free(&ctx->irq_handle); + break; + + case RTTST_IRQBENCH_HANDLER: + ret = rtdm_irq_request(&ctx->irq_handle, config->port_irq, + rt_irqbench_direct_irq, 0, + "irqbench", ctx); + break; + + default: + ret = -EINVAL; + break; + } + if (ret < 0) + goto unlock_start_out; + + ctx->mode = config->mode; + + memset(&ctx->stats, 0, sizeof(ctx->stats)); + + rtdm_irq_enable(&ctx->irq_handle); + + /* Arm IRQ */ + switch (ctx->port_type) { + case RTTST_IRQBENCH_SERPORT: + outb(IER_MODEM, IER(ctx)); + break; + + case RTTST_IRQBENCH_PARPORT: + outb(STAT_STROBE, CTRL(ctx)); + break; + } + + unlock_start_out: + up(&ctx->nrt_mutex); + break; + } + + case RTTST_RTIOC_IRQBENCH_STOP: + down(&ctx->nrt_mutex); + ret = rt_irqbench_stop(ctx); + up(&ctx->nrt_mutex); + break; + + case RTTST_RTIOC_IRQBENCH_GET_STATS: { + struct rttst_irqbench_stats *usr_stats; + + usr_stats = (struct rttst_irqbench_stats *)arg; + + if (user_info) { + if (!rtdm_rw_user_ok(user_info, usr_stats, + sizeof(struct rttst_irqbench_stats)) || + rtdm_copy_to_user(user_info, usr_stats, + &ctx->stats, + sizeof(struct rttst_irqbench_stats))) + ret = -EFAULT; + } else + *usr_stats = ctx->stats; + break; + } + + case RTTST_RTIOC_IRQBENCH_WAIT_IRQ: + ret = -ENOSYS; + break; + + case RTTST_RTIOC_IRQBENCH_REPLY_IRQ: + rt_irqbench_hwreply(ctx); + break; + + default: + ret = -ENOTTY; + } + + return ret; +} + + +static int rt_irqbench_ioctl_rt(struct rtdm_dev_context *context, + rtdm_user_info_t *user_info, int request, + void *arg) +{ + struct rt_irqbench_context *ctx; + int ret = 0; + + + ctx = (struct rt_irqbench_context *)context->dev_private; + + switch (request) { + case RTTST_RTIOC_IRQBENCH_WAIT_IRQ: + ret = rtdm_event_wait(&ctx->irq_event); + break; + + case RTTST_RTIOC_IRQBENCH_REPLY_IRQ: + rt_irqbench_hwreply(ctx); + break; + + case RTTST_RTIOC_IRQBENCH_START: + case RTTST_RTIOC_IRQBENCH_STOP: + case RTTST_RTIOC_IRQBENCH_GET_STATS: + ret = -ENOSYS; + break; + + default: + ret = -ENOTTY; + } + + return ret; +} + + +static struct rtdm_device device = { + struct_version: RTDM_DEVICE_STRUCT_VER, + + device_flags: RTDM_NAMED_DEVICE, + context_size: sizeof(struct rt_irqbench_context), + device_name: "", + + open_rt: NULL, + open_nrt: rt_irqbench_open, + + ops: { + close_rt: NULL, + close_nrt: rt_irqbench_close, + + ioctl_rt: rt_irqbench_ioctl_rt, + ioctl_nrt: rt_irqbench_ioctl_nrt, + + read_rt: NULL, + read_nrt: NULL, + + write_rt: NULL, + write_nrt: NULL, + + recvmsg_rt: NULL, + recvmsg_nrt: NULL, + + sendmsg_rt: NULL, + sendmsg_nrt: NULL, + }, + + device_class: RTDM_CLASS_TESTING, + device_sub_class: RTDM_SUBCLASS_IRQBENCH, + driver_name: "xeno_irqbench", + driver_version: RTDM_DRIVER_VER(0, 1, 0), + peripheral_name: "IRQ Latency Benchmark", + provider_name: "Jan Kiszka", + proc_name: device.device_name, +}; + +int __init __timerbench_init(void) +{ + int ret; + + do { + snprintf(device.device_name, RTDM_MAX_DEVNAME_LEN, "rttest%d", + start_index); + ret = rtdm_dev_register(&device); + + start_index++; + } while (ret == -EEXIST); + + return ret; +} + + +void __exit __timerbench_exit(void) +{ + rtdm_dev_unregister(&device, 1000); +} + + +module_init(__timerbench_init); +module_exit(__timerbench_exit); Index: xenomai/src/testsuite/Makefile.am =================================================================== --- xenomai.orig/src/testsuite/Makefile.am +++ xenomai/src/testsuite/Makefile.am @@ -1 +1 @@ -SUBDIRS = latency switchbench cyclic switchtest +SUBDIRS = latency switchbench cyclic switchtest irqbench Index: xenomai/src/testsuite/irqbench/Makefile.am =================================================================== --- /dev/null +++ xenomai/src/testsuite/irqbench/Makefile.am @@ -0,0 +1,47 @@ +testdir = $(prefix)/testsuite/irqbench + +test_PROGRAMS = irqloop + +if XENO_TARGET_ARCH_I386 +test_PROGRAMS += irqbench +endif + + +irqloop_SOURCES = irqloop.c + +irqloop_CPPFLAGS = \ + -I$(top_srcdir)/include/posix \ + $(XENO_USER_CFLAGS) \ + -I$(top_srcdir)/include + +irqloop_LDFLAGS = \ + $(XENO_POSIX_WRAPPERS) \ + $(XENO_USER_LDFLAGS) + +irqloop_LDADD = \ + -lpthread \ + ../../skins/posix/.libs/libpthread_rt.a + + +irqbench_SOURCES = irqbench.c + +irqbench_CPPFLAGS = \ + $(XENO_USER_CFLAGS) \ + -I$(top_srcdir)/include + +irqbench_LDFLAGS = $(XENO_USER_LDFLAGS) + + +install-data-local: + $(mkinstalldirs) $(DESTDIR)$(testdir) + $(INSTALL_DATA) $(srcdir)/runinfo $(DESTDIR)$(testdir)/.runinfo + @echo "\$${DESTDIR}$(prefix)/bin/xeno-load \$$*" > $(DESTDIR)$(testdir)/run + @chmod +x $(DESTDIR)$(testdir)/run + +uninstall-local: + $(RM) $(DESTDIR)$(testdir)/.runinfo $(DESTDIR)$(testdir)/run + +run: all + @$(top_srcdir)/scripts/xeno-load --verbose + +EXTRA_DIST = runinfo Index: xenomai/src/testsuite/irqbench/irqbench.c =================================================================== --- /dev/null +++ xenomai/src/testsuite/irqbench/irqbench.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2006 Jan Kiszka . + * + * Xenomai is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Xenomai is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xenomai; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#define SERPORT 0 +#define PARPORT 1 + +/* --- Serial port --- */ + +#define MCR_DTR 0x01 +#define MCR_RTS 0x02 +#define MCR_OUT2 0x08 + +#define MSR_DELTA 0x0F + +#define LCR(base) (base + 3) /* Line Control Register */ +#define MCR(base) (base + 4) /* Modem Control Register */ +#define LSR(base) (base + 5) /* Line Status Register */ +#define MSR(base) (base + 6) /* Modem Status Register */ + +/* --- Parallel port --- */ + +#define CTRL_INIT 0x04 + +#define STAT_STROBE 0x10 + +#define DATA(base) (base + 0) /* Data register */ +#define STAT(base) (base + 1) /* Status register */ +#define CTRL(base) (base + 2) /* Control register */ + +double tsc2ns_scale; +long long min_lat = LONG_MAX; +long long max_lat = LONG_MIN; +long long avg_lat = 0; +long outer_loops = 0; +int warmup = 1; +int terminate = 0; + +static inline long long rdtsc(void) +{ + unsigned long long tsc; + + __asm__ __volatile__("rdtsc" : "=A" (tsc)); + return tsc; +} + + +static long tsc2ns(long long tsc) +{ + if ((tsc > LONG_MAX) || (tsc < LONG_MIN)) { + fprintf(stderr, "irqbench: overflow (%lld ns)!\n", + (long long)(tsc2ns_scale * (double)tsc)); + exit(2); + } + return (long)(tsc2ns_scale * (double)tsc); +} + + +static inline long long ns2tsc(long long ns) +{ + return (long long)(((double)ns) / tsc2ns_scale); +} + + +void calibrate_tsc(void) +{ + FILE *proc; + char *lineptr = NULL; + size_t len; + double cpu_mhz; + + proc = fopen("/proc/cpuinfo", "r"); + if (proc == NULL) { + perror("irqbench: Unable to open /proc/cpuinfo"); + exit(1); + } + + while (getline(&lineptr, &len, proc) != -1) + if (strncmp(lineptr, "cpu MHz", 7) == 0) { + sscanf(strchr(lineptr, ':') + 1, "%lf", &cpu_mhz); + break; + } + + if (lineptr) + free(lineptr); + fclose(proc); + + printf("CPU frequency: %.3lf MHz\n", cpu_mhz); + + tsc2ns_scale = 1000.0 / cpu_mhz; +} + + +void sighand(int signal) +{ + if (warmup) + exit(0); + else + terminate = 1; +} + + +int main(int argc, char *argv[]) +{ + int port_type = SERPORT; + unsigned long port_ioaddr = 0x3F8; + long long period = 100000; + long long timeout; + long long start, delay; + unsigned int toggle; + int trigger_trace = 0; + int c; + + + signal(SIGINT, sighand); + signal(SIGTERM, sighand); + signal(SIGHUP, sighand); + signal(SIGALRM, sighand); + + calibrate_tsc(); + + while ((c = getopt(argc,argv,"p:T:o:a:f")) != EOF) + switch (c) { + case 'p': + period = atoi(optarg) * 1000; + break; + + case 'T': + alarm(atoi(optarg)); + break; + + case 'o': + port_type = atoi(optarg); + break; + + case 'a': + port_ioaddr = strtol(optarg, NULL, + (strncmp(optarg, "0x", 2) == 0) ? 16 : 10); + break; + + case 'f': + trigger_trace = 1; + break; + + default: + fprintf(stderr, "usage: irqbench [options]\n" + " [-p ] # signal period, default=100 us\n" + " [-T ] # default=0, so ^C to end\n" + " [-o ] # 0=serial (default), 1=parallel\n" + " [-a ] # default=0x3f8\n" + " [-f] # freeze trace for each new max latency\n"); + exit(2); + } + + if (iopl(3) < 0) { + fprintf(stderr, "irqbench: superuser permissions required\n"); + exit(1); + } + mlockall(MCL_CURRENT | MCL_FUTURE); + + switch (port_type) { + case SERPORT: + toggle = MCR_OUT2; + inb(MSR(port_ioaddr)); + break; + + case PARPORT: + toggle = 0xAA; + outb(0xAA, DATA(port_ioaddr)); + outb(CTRL_INIT, CTRL(port_ioaddr)); + break; + + default: + fprintf(stderr, "irqbench: invalid port type\n"); + exit(1); + } + + period = ns2tsc(period); + + printf("Port type: %s\n" + "Port address: 0x%lx\n\n", + (port_type == SERPORT) ? "serial" : "parallel", port_ioaddr); + + printf("Waiting on target...\n"); + + while (1) + if (port_type == SERPORT) { + toggle ^= MCR_RTS; + outb(toggle, MCR(port_ioaddr)); + usleep(100000); + if ((inb(MSR(port_ioaddr)) & MSR_DELTA) != 0) + break; + } else { + int status = inb(STAT(port_ioaddr)); + + toggle ^= 0xFF; + outb(toggle, DATA(port_ioaddr)); + if (inb(STAT(port_ioaddr)) != status) + break; + } + + printf("Warming up...\n"); + + while (!terminate) { + long long loop_timeout = rdtsc() + ns2tsc(1000000000LL); + long loop_avg = 0; + int inner_loops; + + for (inner_loops = 0; rdtsc() < loop_timeout; inner_loops++) { + long lat; + + __asm__ __volatile__("cli"); + + if (port_type == SERPORT) { + start = rdtsc(); + + toggle ^= MCR_RTS; + outb(toggle, MCR(port_ioaddr)); + + timeout = start + period * 100; + while (((inb(MSR(port_ioaddr)) & MSR_DELTA) == 0) && + (rdtsc() < timeout)); + + delay = rdtsc() - start; + } else { + int status = inb(STAT(port_ioaddr)); + + start = rdtsc(); + + toggle ^= 0xFF; + outb(toggle, DATA(port_ioaddr)); + + timeout = start + period * 100; + while ((inb(STAT(port_ioaddr)) == status) && + (rdtsc() < timeout)); + + delay = rdtsc() - start; + } + + if (!warmup) { + lat = tsc2ns(delay); + + loop_avg += lat; + if (lat < min_lat) + min_lat = lat; + if (lat > max_lat) { + max_lat = lat; + if (trigger_trace) { + if (port_type == SERPORT) { + toggle ^= MCR_DTR; + outb(toggle, MCR(port_ioaddr)); + } else { + // todo + } + } + } + } + + __asm__ __volatile__("sti"); + + while (rdtsc() < start + period); + } + if (!warmup && !terminate) { + loop_avg /= inner_loops; + + printf("%.3f / %.3f / %.3f us\n", + ((double)min_lat) / 1000.0, ((double)loop_avg) / 1000.0, + ((double)max_lat) / 1000.0); + + avg_lat += loop_avg; + outer_loops++; + } else + warmup = 0; + } + + avg_lat /= outer_loops; + printf("---\n%.3f / %.3f / %.3f us\n", + ((double)min_lat) / 1000.0, ((double)avg_lat) / 1000.0, + ((double)max_lat) / 1000.0); + + return 0; +} Index: xenomai/src/testsuite/irqbench/irqloop.c =================================================================== --- /dev/null +++ xenomai/src/testsuite/irqbench/irqloop.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2006 Jan Kiszka . + * + * Xenomai is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Xenomai is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xenomai; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include + +static int benchdev; +static int terminate; + +void *irq_thread(void *arg) +{ + struct sched_param param = { .sched_priority = (int)arg }; + + pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); + + while (1) { + if (ioctl(benchdev, RTTST_RTIOC_IRQBENCH_WAIT_IRQ) || + ioctl(benchdev, RTTST_RTIOC_IRQBENCH_REPLY_IRQ)) + break; + } + + param.sched_priority = 0; + pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶m); + + return NULL; +} + + +void sighand(int sig) +{ + terminate = 1; +} + + +int main(int argc, char *argv[]) +{ + const char *mode_name[] = + { "user-space task", "kernel-space task", "IRQ handler" }; + const char *port_type_name[] = { "serial", "parallel" }; + char devname[RTDM_MAX_DEVNAME_LEN]; + int benchdev_no = 0; + struct rttst_irqbench_config config = { + mode: RTTST_IRQBENCH_USER_TASK, + priority: sched_get_priority_max(SCHED_FIFO), + calibration_loops: 0, + port_type: RTTST_IRQBENCH_SERPORT, + port_ioaddr: 0x3f8, + port_irq: 4 + }; + struct rttst_irqbench_stats stats; + unsigned long long last_received = 0; + pthread_t thr; + int c; + + + while ((c = getopt(argc,argv,"D:t:P:o:a:i:")) != EOF) + switch (c) { + case 'D': + benchdev_no = atoi(optarg); + break; + + case 't': + config.mode = atoi(optarg); + break; + + case 'P': + config.priority = atoi(optarg); + break; + + case 'o': + config.port_type = atoi(optarg); + break; + + case 'a': + config.port_ioaddr = strtol(optarg, NULL, + (strncmp(optarg, "0x", 2) == 0) ? 16 : 10); + break; + + case 'i': + config.port_irq = atoi(optarg); + break; + + default: + fprintf(stderr, "usage: irqloop [options]\n" + " [-D ] # number of testing device, default=0\n" + " [-t ] # 0=user task (default), 1=kernel task, 2=IRQ\n" + " [-P ] # task priority (test mode 0 and 1 only)\n" + " [-o ] # 0=serial (default), 1=parallel\n" + " [-a ] # default=0x3f8\n" + " [-i ] # default=4\n"); + exit(2); + } + + signal(SIGINT, sighand); + signal(SIGTERM, sighand); + signal(SIGHUP, sighand); + + mlockall(MCL_CURRENT|MCL_FUTURE); + + snprintf(devname, RTDM_MAX_DEVNAME_LEN, "/dev/rttest%d", benchdev_no); + benchdev = open(devname, O_RDWR); + if (benchdev < 0) { + perror("irqloop: failed to open benchmark device"); + fprintf(stderr, "(modprobe xeno_irqbench?)\n"); + return 1; + } + + if (config.mode == RTTST_IRQBENCH_USER_TASK) { + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); + + pthread_create(&thr, &attr, irq_thread, (void *)config.priority); + } + + if (ioctl(benchdev, RTTST_RTIOC_IRQBENCH_START, &config)) { + perror("irqloop: error starting test"); + goto cleanup; + } + + printf("Test mode: %s\n" + "Port type: %s\n" + "Port address: 0x%lx\n" + "Port IRQ: %d\n\n\n\n", + mode_name[config.mode], port_type_name[config.port_type], + config.port_ioaddr, config.port_irq); + + while (!terminate) { + if (ioctl(benchdev, RTTST_RTIOC_IRQBENCH_GET_STATS, &stats) < 0) { + perror("irqloop: error reading stats"); + break; + } + + if ((last_received > 0) && (stats.irqs_received == last_received)) + break; /* timed out */ + last_received = stats.irqs_received; + + printf("\033[2AReceived IRQs: %lld\nAcknowledged IRQs: %lld\n", + stats.irqs_received, stats.irqs_acknowledged); + usleep(250000); + } + + ioctl(benchdev, RTTST_RTIOC_IRQBENCH_STOP); + + cleanup: + close(benchdev); + if (config.mode == RTTST_IRQBENCH_USER_TASK) { + pthread_cancel(thr); + pthread_join(thr, NULL); + } + + return 0; +} Index: xenomai/src/testsuite/irqbench/runinfo =================================================================== --- /dev/null +++ xenomai/src/testsuite/irqbench/runinfo @@ -0,0 +1 @@ +irqloop:posix+rtdm+irqbench:!./irqloop;popall:control_c Index: xenomai/doc/txt/Makefile.am =================================================================== --- xenomai.orig/doc/txt/Makefile.am +++ xenomai/doc/txt/Makefile.am @@ -2,6 +2,7 @@ txtdocdir = $(datadir)/doc/$(PACKAGE)-$( dist_txtdoc_DATA = \ 16550A-driver.txt \ + irqbench.txt \ pse51-skin.txt \ psos-skin.txt \ rtai-skin.txt \ Index: xenomai/doc/txt/irqbench.txt =================================================================== --- /dev/null +++ xenomai/doc/txt/irqbench.txt @@ -0,0 +1,51 @@ +IRQ Latency Benchmark +--------------------- + +This latency benchmark for external interrupts consists of three parts: + +1. xeno_irqbench (ksrc/drivers/testing) + RTDM driver for the test target to handle and reply to the IRQ events, or + forward them to user-space. + +2. irqloop (src/testsuite/irqbench) + xeno_irqbench control front-end and user-space loop-closer. Runs against + the POSIX skin on the test target. + +3. irqbench (src/testsuite/irqbench) + IRQ triggering and reaction measuring tool for the log system. Runs over + plain Linux on x86 hosts. Note that this tool will only be compiled + automatically with Xenomai if the target architecture is x86. To compile + it manually, invoke + + irqbench.c -o irqbench -O2 + +To link both test and log system, a null-modem cable between 8250-compatible +two RS232 ports is required. The infrastructure for parallel port cross-over +is prepared as well, but yet untested and incomplete. The null-modem cable +must provide at least the following links: + + test target log system + ----------- ---------- + CTS(8) <------ RTS(7) IRQ trigger + RTS(7) ------> CTS(8) reply + + DCD(1) + and/or <------ DTR(4) trace trigger (optional) + DSR(6) + + (Pin number on 9-pin sub-D socket) + +Test and log system can be started in arbitrary order, they will wait on +each other. + +The log system runs each single IRQ measurement with local IRQs disabled. It +will wait up to 100 times the current period on the test system's reply (after +initial successful synchronisation, which happens with IRQs enabled). Note that +these timeouts may disturb other processes or drivers on the log system. + +Keep in mind for test evaluations that, by design, irqbench will not trigger a +new IRQ event before the reply on the previous one has arrived or timed out. +Thus the specified test period may be dynamically expended in case of +overload. But maximum a latency close or above the period will clearly +indicate this. +