ADEOS IPIPE Linux Hard Real Time: Absolute Time (Unix Timestamp) in Kernel Space
This example show how to get absolute time in kernel space without breaking hard real time determinism
provided by the ADEOS IPIPE kernel extension. Usually unixtime can be accessed with "do_gettimeofday"
but this is not hard real time safe, so linux may spend undetermined time to get this time and we
have no way to assure determinism anymore.
UPDATE: If you don't have an APIC on your system, check this page
ADEOS IPIPE Hard Real Time - 8254 Timer Programming
There are 2 modes for this example:
If #define USEPARALLELPORT is set, the timer interrupt service routine (ISR) will just generate
a parallel port interrupt (Make a "null-modem" for the parallel port - connect any output pin (pin 2-9)
with the IRQ pin (pin 10 = ACK) ) and the parallel port ISR will calculate the time.
If #define USEPARALLELPORT is NOT set, the time will be calculated in the timer ISR.
The key function for getting the time in nano-seconds (since we started the adeos domain) is this:
cpu_khz_llu = (unsigned long long)cpu_khz;
diff = currenttsc - starttsc;
mod = do_div(diff, cpu_khz_llu);
mod2 = (unsigned long long)mod * 1000000llu;
dummy = do_div(mod2, cpu_khz_llu);
time = diff * 1000000llu + mod2;
First we get the CPU frequency provided by Linux from the variable "cpu_khz". Then we calculate the
difference of the TSC counter since we started (diff = currenttsc - starttsc;). We do a 64 bit division
to calculate the time elapsed (TSC-count / CPUfrequency = time). In order to get a resolution on
nano-second level, we use the remainder of the previous division, multiply it by 1000000 (actually 1000000000,
but we have the CPU frequency in kHz!) and divide it thru the CPU frequency. Now we sum the number of
seconds in diff (multiplied by 1000000) and the nano-seconds count.
If "#define APIC_TIMER" is present, APIC is modified, if "#define APIC_TIMER" is removed, the regular
Linux tick is used (100Hz - 10ms). It automatically gets the current APIC frequency and restores it when
exiting the module. Furthermore you can set the timer frequency with SAMPLE_FREQUENCY.
Related (e.g. for a data logger project):
Linux Device Driver Kernel Module FIFO (circular buffer)
Adeos Nano Kernel Hard Real Time Double Buffering FIFO
Compile "abstime.c" with "make".
Load the kernel module and you'll see an output like this in your kernel log file:
Dec 17 12:30:17 muon kernel: START
Dec 17 12:30:17 muon kernel: cpu_khz=451031
Dec 17 12:30:17 muon kernel: starttime=1134819017708193000 starttsc=496568282512
Dec 17 12:30:17 muon kernel: I-pipe: Domain TestDomain registered.
Dec 17 12:30:17 muon kernel: Domain TestDomain started.
Dec 17 12:30:17 muon kernel: SAVED APIC FREQ. apic_freq=6264500
Dec 17 12:30:17 muon kernel: CURRENT APIC FREQ. apic_freq=626450000
Dec 17 12:30:17 muon kernel: USING APIC
Dec 17 12:30:18 muon kernel: int=1 currenttsc=497019370123 time=1000125514
Dec 17 12:30:19 muon kernel: int=2 currenttsc=497470413703 time=2000153406
Dec 17 12:30:20 muon kernel: int=3 currenttsc=497921457737 time=3000182304
Dec 17 12:30:21 muon kernel: int=4 currenttsc=498372501714 time=4000211076
Dec 17 12:30:22 muon kernel: int=5 currenttsc=498823545749 time=5000239976
Dec 17 12:30:23 muon kernel: int=6 currenttsc=499274589706 time=6000268704
Developed and tested with kernel 2.4.32 with adeos-ipipe-2.4.31-i386-1.0-00.patch.
Also tested with kernel 2.6.14.3 (adeos-ipipe-2.6.14-i386-1.0-11.patch).
abstime.c
/*
ADEOS IPIPE Hard Real Time: Absolute Time (Unix Timestamp) in Kernel Space
(C) 2005 www.captain.at
do_gettimeofday may break determinism, so we calculate time with TSC (rdtsc)
*/
#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>
#include <asm/hw_irq.h>
// from kernel 2.6
#define do_div(n,base) ({ \
unsigned long __upper, __low, __high, __mod, __base; \
__base = (base); \
asm("":"=a" (__low), "=d" (__high):"A" (n)); \
__upper = __high; \
if (__high) { \
__upper = __high % (__base); \
__high = __high / (__base); \
} \
asm("divl %2":"=a" (__low), "=d" (__mod):"rm" (__base), \
"0" (__low), "1" (__upper)); \
asm("":"=A" (n):"a" (__low),"d" (__high)); \
__mod; \
})
#define SAMPLE_FREQUENCY 1 // in Hz
#define APIC_TIMER // if this is present, use our own APIC frequency
#define PRINTINFO // if this is present, additional info in printed
// if this is present, use the parallelport interrupt
// otherwise use timer interrupt
//#define USEPARALLELPORT
#define BASEPORT 0x378
#define PAR_INT 7
#ifdef LINUX_24
// values for kernel 2.4
#define APIC_TIMER_VECTOR 0xe9
#define APIC_TIMER_IPI 201
#else
// values for kernel 2.6
#define APIC_TIMER_VECTOR 0xf5
#define APIC_TIMER_IPI 213
#endif
// stuff for restoring original APIC frequency
long apic_freq;
#define FREQ_APIC (apic_freq)
#define APIC_ICOUNT ((FREQ_APIC + HZ/2)/HZ)
static struct ipipe_domain this_domain;
#ifndef APIC_TIMER
static struct ipipe_sysinfo sys_info;
#endif
long long parcounter = 0;
long long timercounter = 0;
#define mytick FREQ_APIC / SAMPLE_FREQUENCY
#define rtai_rdtsc() ({ unsigned long long t; \
__asm__ __volatile__( "rdtsc" : "=A" (t)); t; })
unsigned long long starttime;
unsigned long long starttsc;
#ifdef USEPARALLELPORT
void handler(unsigned irq) { // interrupt handler
unsigned long flags;
unsigned long long currenttsc, diff, cpu_khz_llu;
unsigned long long time, mod2;
unsigned long mod, dummy;
currenttsc = rtai_rdtsc();
flags = ipipe_critical_enter (NULL);
parcounter++;
// now calculate the elapsed time in seconds since start
// you can also calculate the absolute time by adding starttime
// "time" will be printed in nano-seconds!
// printk breaks hard real time determinism, so don't use it in
// production
cpu_khz_llu = (unsigned long long)cpu_khz;
diff = currenttsc - starttsc;
mod = do_div(diff, cpu_khz_llu);
mod2 = (unsigned long long)mod * 1000000llu;
dummy = do_div(mod2, cpu_khz_llu);
time = diff * 1000000llu + mod2;
printk("int=%lld currenttsc=%lld time=%lld\n",
parcounter, currenttsc, time);
ipipe_critical_exit (flags);
ipipe_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK);
ipipe_propagate_irq(irq);
}
#endif
void timer_tick (unsigned irq) {
timercounter++;
#ifdef USEPARALLELPORT
// generate interrupt
outb_p(0, BASEPORT);
outb_p(255, BASEPORT);
#else
unsigned long flags;
unsigned long long currenttsc, diff, cpu_khz_llu;
unsigned long long time, mod2;
unsigned long mod, dummy;
currenttsc = rtai_rdtsc();
flags = ipipe_critical_enter (NULL);
parcounter++;
// now calculate the elapsed time in seconds since start
// you can also calculate the absolute time by adding starttime
// "time" will be printed in nano-seconds!
// printk breaks hard real time determinism, so don't use it in
// production
cpu_khz_llu = (unsigned long long)cpu_khz;
diff = currenttsc - starttsc;
mod = do_div(diff, cpu_khz_llu);
mod2 = (unsigned long long)mod * 1000000llu;
dummy = do_div(mod2, cpu_khz_llu);
time = diff * 1000000llu + mod2;
printk("int=%lld currenttsc=%lld time=%lld\n",
parcounter, currenttsc, time);
ipipe_critical_exit (flags);
#endif
ipipe_propagate_irq(irq);
}
static inline void save_apic_frequency (void) {
// save current APIC freq.
apic_freq = apic_read(APIC_TMICT) * HZ;
#ifdef PRINTINFO
printk("SAVED APIC FREQ. apic_freq=%ld\n", apic_freq);
#endif
}
static inline void rtai_setup_periodic_apic (unsigned count, unsigned vector) {
#ifdef PRINTINFO
long tmp;
#endif
apic_read(APIC_LVTT);
apic_write(APIC_LVTT, APIC_LVT_TIMER_PERIODIC | vector);
apic_read(APIC_TMICT);
apic_write(APIC_TMICT, count);
#ifdef PRINTINFO
tmp = apic_read(APIC_TMICT) * HZ;
printk("CURRENT APIC FREQ. apic_freq=%ld\n", tmp);
#endif
}
void domain_entry (void) {
printk("Domain %s started.\n",ipipe_current_domain->name);
#ifdef APIC_TIMER
ipipe_virtualize_irq(ipipe_current_domain, APIC_TIMER_IPI,
&timer_tick, NULL, IPIPE_DYNAMIC_MASK);
save_apic_frequency();
rtai_setup_periodic_apic(mytick, APIC_TIMER_VECTOR);
#ifdef PRINTINFO
printk("USING APIC\n");
#endif
#else
// tmirq = timer irq: IRQ 0 on x86
ipipe_get_sysinfo(&sys_info);
ipipe_virtualize_irq(ipipe_current_domain, sys_info.archdep.tmirq,
&timer_tick, NULL, IPIPE_DYNAMIC_MASK);
#ifdef PRINTINFO
printk("USING IRQ 0\n");
#endif
#endif
#ifdef USEPARALLELPORT
// parallel port irq
ipipe_virtualize_irq(ipipe_current_domain, PAR_INT,&handler,
NULL,IPIPE_DYNAMIC_MASK);
//set port to interrupt mode; pins are output
outb_p(0x10, BASEPORT + 2);
ipipe_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK);
#endif
}
static int __init mod_init (void) {
struct ipipe_domain_attr attr;
struct timeval tv;
printk("START\n");
ipipe_init_attr (&attr);
attr.name = "TestDomain";
attr.priority = IPIPE_ROOT_PRIO + 1;
attr.entry = &domain_entry;
// do_gettimeofday breaks hard real time, but we're outside the
// domain_entry, so it's OK here. Do not attempt to use it
// in the interrupt service routines
do_gettimeofday(&tv);
starttime = (long long)( tv.tv_sec * (long long) 1000000000 +
tv.tv_usec * (long long) 1000 );
starttsc = rtai_rdtsc();
printk("cpu_khz=%ld\n", cpu_khz);
printk("starttime=%lld starttsc=%lld\n", starttime, starttsc);
return ipipe_register_domain(&this_domain,&attr);
}
static void __exit mod_exit (void) {
#ifdef APIC_TIMER
rtai_setup_periodic_apic(APIC_ICOUNT, LOCAL_TIMER_VECTOR);
#endif
ipipe_unregister_domain(&this_domain);
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
Makefile for abstime.c: (kernel 2.4)
TARGET := abstime
INCLUDE := -I/lib/modules/`uname -r`/build/include
CFLAGS := -O2 -Wall -DMODULE -DUSEFIFO -D__KERNEL__ -DLINUX
CC := gcc
${TARGET}.o: ${TARGET}.c
$(CC) $(CFLAGS) ${INCLUDE} -c ${TARGET}.c
Makefile for abstime.c: (kernel 2.6)
obj-m := abstime.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
Insert the module with: (kernel 2.4)
# insmod ./abstime.o
Insert the module with: (kernel 2.6)
# insmod ./abstime.ko
Remove the module with:
# rmmod abstime
Last-Modified: Thu, 14 Sep 2006 18:26:52 GMT