ADEOS IPIPE Hard Real Time - Interrupt jitter latency test
Here we have an ADEOS IPIPE example that uses and modifies the 8254 timer interrupt
to have a custom timer interrupt frequency in order to measure the timer interrupt
jitter and also the max. latency.
We reprogram the 8254 timer to a higher (sampling) frequency. In order to maintain
a 100Hz timer tick to linux, we only pass the timer interrupt to linux
after PROPAGATE_INT_AFTER_COUNTS timer ticks have occured.
For further details see the adeos-ipipe 8254 timer page
Also see: Stress tests
If you need to disable SMI interrupts, check the captain.at page at
High latencies with SMI not disabled
If SMI interrupts are not disbaled, you've likely experienced an interrupt behavious like this.
the interrupt jitter is well between the bounds of +/-10μs, but there are the SMI interrupts
of 200μs and more:
LATCH=4773
HZ=250
SAMPLE_FREQUENCY=20000
SAMPLE_FREQUENCY_NS=50000
NEWLATCH=59
PROPAGATE_INT_AFTER_COUNTS=80
CANCEL_INT_AFTER_COUNTS=89
I-pipe: Domain TestDomain registered.
Domain TestDomain started.
scaller set to 1400
period set to 50
I-pipe: Domain TestDomain unregistered.
domain wakeup jitter for timer interrupt
domain should have woken up every 50 us
min: 40 us, max: 60 us (21381982 samples)
diff : samples
-10 us: 1
-8 us: 3
-7 us: 55
-6 us: 324
-5 us: 1780
-4 us: 14408
-3 us: 83385
-2 us: 233056
-1 us: 3731377
0 us: 16680530
1 us: 431904
2 us: 157515
3 us: 42330
4 us: 4506
5 us: 669
6 us: 124
7 us: 12
8 us: 2
10 us: 1
197 us: 1
200 us: 2
203 us: 1
In order to eliminate those nasty SMI interrupts, there is a pretty simple linux
kernel module at High latencies with SMI not disabled.
With that module you'll achieve those jitter/latency values you're expecting. ADEOS-IPIPE is
actually the best you can get - just depends on the hardware you're using.
LATCH=4773
HZ=250
SAMPLE_FREQUENCY=20000
SAMPLE_FREQUENCY_NS=50000
NEWLATCH=59
PROPAGATE_INT_AFTER_COUNTS=80
CANCEL_INT_AFTER_COUNTS=89
I-pipe: Domain TestDomain registered.
Domain TestDomain started.
scaller set to 1400
period set to 50
I-pipe: Domain TestDomain unregistered.
domain wakeup jitter for timer interrupt
domain should have woken up every 50 us
min: 40 us, max: 60 us (3470912 samples)
Feb 11 10:47:19 muon kernel:
diff : samples
-10 us: 1
-8 us: 7
-7 us: 41
-6 us: 126
-5 us: 1997
-4 us: 24572
-3 us: 60669
-2 us: 41353
-1 us: 575360
0 us: 2606700
1 us: 48189
2 us: 44075
3 us: 56304
4 us: 11012
5 us: 363
6 us: 105
7 us: 29
8 us: 7
9 us: 1
10 us: 1
Finally: There is a Makefile below, so compilation should be no problem if you've
got a vital kernel module building system.
Insert the ipipe_irq_jitter module with
# insmod ./ipipe_irq_jitter.ko
-or-
# insmod ./ipipe_irq_jitter.o
depending on your kernel version.
After unloading the module, you'll see the histogram in the kernel log.
Developed and tested with kernel 2.4.32 (adeos-ipipe-2.4.32-i386-1.1-02.patch). Also tested on
kernel 2.6.15 with the IPIPE 1.2 patch.
For further details see the comments in the source code.
NOTE: If you test on kernels 2.4 and 2.6, make sure you do a "make clean" and everything should compile well.
ipipe_irq_jitter.c
/*
ADEOS IPIPE kernel module with 8254 timer usage
+ interrupt jitter/latency measurement
Module (C) 2006 www.captain.at
Interrupt jitter/latency measurement (C) 2004 Der Herr Hofrat
*/
#include <linux/version.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/apic.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
// for 2.4 ipipe
#include <asm/hw_irq.h>
#define SAMPLE_FREQUENCY 20000
static struct ipipe_sysinfo sys_info;
static struct ipipe_domain this_domain;
long long parcounter = 0;
long long timercounter = 0;
unsigned int timerprop = 0;
unsigned int subtimer = 0;
#define PIT_CH0 0x40
#define PIT_MODE 0x43
#define RTHAL_8254_IRQ 0
#define NEWLATCH ((int)((LATCH * HZ) / SAMPLE_FREQUENCY))
#define PROPAGATE_INT_AFTER_COUNTS ((int)(LATCH / NEWLATCH))
#define SAMPLE_FREQUENCY_NS ((int)(1000000000/SAMPLE_FREQUENCY))
#define CANCEL_INT_AFTER_COUNTS ((int)(1.0/((1.0/HZ)/((NEWLATCH*\
PROPAGATE_INT_AFTER_COUNTS)/1193180.0)-1.0)))
#define PERIOD (1000000/SAMPLE_FREQUENCY)
#define rtai_rdtsc() ({ unsigned long long t; \
__asm__ __volatile__( "rdtsc" : "=A" (t)); t; })
#define NUMHIST 300
static long min,max;
static unsigned long total_samp=0;
static unsigned long period;
unsigned int graph[2][NUMHIST];
unsigned long scaller;
unsigned long long last,now;
long diff;
int isfirstrun = 1;
void timer_tick (unsigned irq) {
unsigned long flags;
flags = ipipe_critical_enter(NULL);
last = now;
//hwtimer(now);
now = rtai_rdtsc();
if (isfirstrun) {
last = now;
isfirstrun = 0;
ipipe_propagate_irq(irq);
return;
}
timercounter++;
timerprop++;
/* allow it to be handled by 32bit instruction */
diff = ((unsigned long)(now - last)/scaller) - period;
/* don't let total_samp role around at 4G */
if (total_samp != -1) {
if (diff <= 0) {
/* fill in the histogram */
if (-diff < NUMHIST) {
graph[0][-diff]++;
} else {
printk("Out of bounds %ld us\n", diff);
}
/* record min even if out of bounds */
if ( diff < min) { min = diff; }
} else if (diff > 0) {
if (diff <= NUMHIST) {
graph[1][diff-1]++;
} else {
printk("Out of bounds %ld us\n", diff);
}
/* record max even if out of bounds */
if ( diff > max) {
max = diff;
}
}
total_samp++;
}
ipipe_critical_exit (flags);
// pass timer interrupt to linux (100Hz)
// if we check with >= we run too fast, but we skip an interrupt later
if ( timerprop >= PROPAGATE_INT_AFTER_COUNTS ) {
subtimer++;
if (subtimer <= CANCEL_INT_AFTER_COUNTS) {
ipipe_propagate_irq(irq);
} else {
subtimer = 0;
}
timerprop = 0;
}
}
static inline void setup_8254 (void) {
// either use the adeos-ipipe function to programm the 8254,
// or do it ourselfs
ipipe_tune_timer(SAMPLE_FREQUENCY_NS, 0);
/*
unsigned long flags;
flags = ipipe_critical_enter(NULL);
period = NEWLATCH;
if (period > LATCH) period = LATCH;
outb(0x34,PIT_MODE);
outb(period & 0xff,PIT_CH0);
outb(period >> 8,PIT_CH0);
ipipe_critical_exit (flags);
*/
}
static void domain_entry (void) {
printk("Domain %s started.\n",ipipe_current_domain->name); //
// setup 8254 timer
setup_8254();
// grab 8254 interrupt
ipipe_virtualize_irq(ipipe_current_domain, RTHAL_8254_IRQ,
(ipipe_irq_handler_t)&timer_tick, NULL, NULL, IPIPE_DYNAMIC_MASK);
// disable interrupt propagation to other domains (e.g. linux)
ipipe_control_irq(RTHAL_8254_IRQ,0,IPIPE_HANDLE_MASK);
ipipe_get_sysinfo(&sys_info);
scaller = (unsigned long)sys_info.cpufreq / 1000000;
period = PERIOD;
printk("scaller set to %ld\n",(unsigned long) scaller);
printk("period set to %ld\n",(long) period);
min=100;
max=-100;
}
static int __init mod_init (void) {
struct ipipe_domain_attr attr;
printk("LATCH=%d\n", LATCH);
printk("HZ=%d\n", HZ);
printk("SAMPLE_FREQUENCY=%d\n", SAMPLE_FREQUENCY);
printk("SAMPLE_FREQUENCY_NS=%d\n", SAMPLE_FREQUENCY_NS);
printk("NEWLATCH=%d\n", NEWLATCH);
printk("PROPAGATE_INT_AFTER_COUNTS=%d\n", PROPAGATE_INT_AFTER_COUNTS);
printk("CANCEL_INT_AFTER_COUNTS=%d\n", CANCEL_INT_AFTER_COUNTS);
// start our real time domain
ipipe_init_attr (&attr);
attr.name = "TestDomain";
attr.priority = IPIPE_ROOT_PRIO + 200;
attr.entry = &domain_entry;
return ipipe_register_domain(&this_domain,&attr);
}
static void __exit mod_exit (void) {
int i;
// reset 8254 timer interrupt to default value
ipipe_tune_timer(10000000,IPIPE_RESET_TIMER);
// unregister our domain
ipipe_unregister_domain(&this_domain);
/* dump the results via printk - not very elegant....*/
printk("domain wakeup jitter for timer interrupt\n");
printk("domain should have woken up every %ld us\n",
(unsigned long)(1000000/SAMPLE_FREQUENCY));
printk(" min: %ld us, max: %ld us (%ld samples)\n",
min+period,max+period,total_samp);
printk("\n\tdiff : samples\n");
for (i = (NUMHIST - 1); i >= 0; i--){
if (graph[0][i] > 0) {
printk("\t%4d us: %4d\n", -i, graph[0][i]);
}
}
for (i = 0; i < NUMHIST; i++){
if (graph[1][i] > 0) {
printk("\t%4d us: %4d\n", i+1, graph[1][i]);
}
}
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
Makefile
UNAME := $(shell uname -r)
KERNEL26 := 2.6
KERNELVERSION := $(findstring $(KERNEL26),$(UNAME))
all::
ifeq ($(KERNELVERSION),2.6)
obj-m := ipipe_irq_jitter.o
INCLUDE := -I/usr/include/asm/mach-default/
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all::
$(MAKE) -C $(KDIR) $(INCLUDE) SUBDIRS=$(PWD) modules
else
TARGET := ipipe_irq_jitter
INCLUDE := -I/lib/modules/`uname -r`/build/include -I/usr/include/asm/mach-default/
CFLAGS := -O2 -Wall -DMODULE -D__KERNEL__ -DLINUX
CC := gcc
all:: ${TARGET}.o
${TARGET}.o: ${TARGET}.c
$(CC) $(CFLAGS) ${INCLUDE} -c ${TARGET}.c
endif
clean::
$(RM) .ipipe_irq_jitter* *.cmd *.o *.ko *.mod.c
$(RM) -R .tmp*
Last-Modified: Sat, 11 Feb 2006 03:04:17 GMT