Captain's Universe Home
Captain's Universe Home
Cosmic Ray Muon DetectorTeleGarden Pages
Time on MarsBryophyllum Plants
Jupiter Radio AstronomyAncient Pages
Salzburg Tourist GuideEarth Magnetometer
  H O M E     AJAX & MORE     LINUX & MORE     RTAI     XENOMAI     ADEOS IPIPE      
    JAVA & BROWSERS     *NIX     ELECTRONICS     REVIEWS     ARTEMIA     FAIRY SHRIMP      


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

Google
 
Web www.captain.at
go to top
© 1996-2010 . All rights reserved.
No reproduction, distribution, publishing or transmission of the copyrighted materials at this site is permitted. Policy
go to top