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 Nano Linux Kernel Hard Real Time Parallel Port ISR with Double Buffering FIFO



This is a simple implementation of a ADEOS/Linux kernel module / device driver circular buffer (FIFO) suitable for sampling data in hard real time and caching it in kernel space. Data is read from the kernel module via a user-space application via a /proc file entry.

This example uses double buffering, so we can use much higher sampling frequencies than with the single buffer shown in Linux Device Driver Kernel Module FIFO (circular buffer). In the single buffer example, we are not allowed to write to the buffer while reading from it, since writing can modify the read_pos and we may lose data. This example allows to write data to one buffer, while we read data from the other buffer.

Also see:
The timer interrupt service routine generates the parallel port interrupts. The parallel port ISR writes data to the buffer, depending on which buffer is actually active. If "buffer 1" is full, it switches to "buffer 2" and data can be read from "buffer 1" without disturbing data writing to "buffer 2".

This example modifies the APIC. The original IRQ 0 functionality remains - 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. You can set the timer frequency with SAMPLE_FREQUENCY.

UPDATE: If you don't have an APIC on your system, check this page ADEOS IPIPE Hard Real Time - 8254 Timer Programming

In order to test this example successfully, you need to make a "null-modem" for the parallel port - connect any output pin (pin 2-9) with the IRQ pin (pin 10 = ACK). Don't increase the SAMPLE_FREQUENCY too much or your machine will freeze - I've tested a max. of 50000 (50kHz) on a PIII/450MHz box. So it is perfectly suitable for sampling audio data or logging other high speed data in hard real time. Just make sure you make the buffers large enough to prevent data loss due slow reading and writing to disk.


Developed and tested with kernel 2.4.31 (adeos r18c1). Also tested with kernel 2.6.10 (adeos r12).
For further details see the comments in the source code.

TROUBLESHOOTING:
If you see warnings like this during "make"
*** Warning: "timeint" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "oldtimeint" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "latency" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "buf2" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "buf" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "timenew" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "timeold" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "timediff" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "proc_parint" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "proc_parint_data" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "apic_freq" [/home/adeos/doublefifo/doublefifo] is COMMON symbol
*** Warning: "__generic_copy_from_user" [/home/adeos/doublefifo/doublefifo.ko] undefined!
*** Warning: "adeos_propagate_irq" [/home/adeos/doublefifo/doublefifo.ko] undefined!
you most likely compiled the module for kernel 2.4 before and now you compile for kernel 2.6.
Do a "make clean" and everything should compile well.

doublefifo.c
/*
	ADEOS kernel module with circular buffer (FIFO)
	
	Module (C) 2005 www.captain.at
	FIFO (C) 2005 www.opersys.com	(LRTBF http://www.opersys.com/lrtbf/)
	
	USAGE:
	* Compile the kernel module with "make"
	* Make a "null-modem" for the parallel port - connect any output pin (pin 2-9)
	*              with the IRQ pin (pin 10 = ACK)
	* Load it with # insmod ./doublefifo.ko or # insmod ./doublefifo.o
	* Start "user" - end it with CTRL-C; see "mydata" for the data read from
		kernel space
	* Test writing to the kernel module via the proc file with:
		# echo "test" > /proc/simplefifo/data
		(check the kernel log for the message)
*/
#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>

#define APIC_TIMER	// if this is present, use our own APIC frequency
#define WRITEPARCOUNTER	// if enabled, parcounter is written to the buffer,
                        // if not, latency info

//#define DEBUGIT       // prints info on every interrupt (use only with very
                        // low SAMPLE_FREQUENCY)
//#define DEBUGITSWITCH	// prints info on every buffer switch
//#define DEBUGITREAD	// prints info on very read operation on the proc file
//#define PRINTINFO		// prints additional info

#define MAX_INTS 10000000
#define BUFFER_SIZE 128000
#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 adomain_t this_domain;
#ifndef APIC_TIMER
static adsysinfo_t sys_info;
#endif
struct proc_dir_entry *proc_parint;
struct proc_dir_entry *proc_parint_data;

long long parcounter = 0;
long long timercounter = 0;

#define SAMPLE_FREQUENCY 22050
#define mytick FREQ_APIC / SAMPLE_FREQUENCY

#define rtai_rdtsc() ({ unsigned long long t; __asm__ __volatile__( "rdtsc" : "=A" (t)); t; })
#define MAX_STR_LEN (sizeof(int))
int buf[BUFFER_SIZE];
int buf2[BUFFER_SIZE];

int noproc = 0;
unsigned long long timeold, timenew, timediff, timeint, latency, oldtimeint;
volatile static unsigned int read_pos = 0, write_pos = 0;
volatile static unsigned int read_pos2 = 0, write_pos2 = 0;
static int read_proc_buf_pos = 0;
volatile static unsigned int whichbuf = 1;

static inline int inc_read_pos (void) {
	read_pos = (read_pos + 1) % BUFFER_SIZE;
	return read_pos;
}

static inline int inc_write_pos (void) {
	unsigned int newpos = (write_pos + 1) % BUFFER_SIZE;
	if (read_pos == newpos) {// buffer full ?
		inc_read_pos(); // yes, move read_pos one position ahead of write_pos
	}
	write_pos = newpos;
	if (newpos == (BUFFER_SIZE - 1)) {
		whichbuf = 0;
		if ( (write_pos2 == 0) && (read_pos2 == 0) ) {
			// buffer fully read
		} else {
			printk("DATA LOSS: SWITCHING TO BUF2 BEFORE BUF2 WAS READ COMPLETELY\n");
		}
#ifdef DEBUGITSWITCH
		printk("switched to buffer buf2 read_pos=%d write_pos=%d read_pos2=%d \
					write_pos2=%d parcounter=%d\n",
					read_pos, write_pos, read_pos2, write_pos2, (int)parcounter);
#endif
	}
	return write_pos;
}

static inline int inc_read_pos2 (void) {
	read_pos2 = (read_pos2 + 1) % BUFFER_SIZE;
	return read_pos2;
}

static inline int inc_write_pos2 (void) {
	unsigned int newpos = (write_pos2 + 1) % BUFFER_SIZE;
	if (read_pos2 == newpos) {// buffer full ?
		inc_read_pos2(); // yes, move read_pos one position ahead of write_pos
	}
	write_pos2 = newpos;
	if (newpos == (BUFFER_SIZE - 1)) {
		whichbuf = 1;
		if ( (write_pos == 0) && (read_pos == 0) ) {
			// buffer fully read
		} else {
			printk("DATA LOSS: SWITCHING TO BUF BEFORE BUF WAS READ COMPLETELY\n");
		}
#ifdef DEBUGITSWITCH
		printk("switched to buffer buf read_pos=%d write_pos=%d read_pos2=%d \
					write_pos2=%d parcounter=%d\n",
					read_pos, write_pos, read_pos2, write_pos2, (int)parcounter);
#endif
	}
	return write_pos2;
}

void handler(unsigned irq) {
	unsigned long flags;

	flags = adeos_critical_enter (NULL);

	timeint = rtai_rdtsc();
	latency = timeint - oldtimeint;
	parcounter++;

	if (whichbuf == 1) {
#ifdef DEBUGIT
		printk("writing to buf read_pos=%d write_pos=%d parcounter=%d\n",
				read_pos, write_pos, (int)parcounter);
#endif
#ifdef WRITEPARCOUNTER
		buf[write_pos] = (int)parcounter;
		inc_write_pos();
#else
		buf[write_pos] = (int)latency; // time of timer IRQ
		inc_write_pos();
#endif
	} else {
#ifdef DEBUGIT
		printk("writing to buf2 read_pos2=%d write_pos2=%d parcounter=%d\n",
				read_pos2, write_pos2, (int)parcounter);
#endif
#ifdef WRITEPARCOUNTER
		buf2[write_pos2] = (int)parcounter;
		inc_write_pos2();
#else
		buf2[write_pos2] = (int)latency; // time of timer IRQ
		inc_write_pos2();
#endif
	}	

	adeos_critical_exit (flags);
	oldtimeint = timeint;
	
	adeos_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK);
	adeos_propagate_irq(irq);
}

void timer_tick (unsigned irq) {
	timercounter++;
	if (timercounter < MAX_INTS) {
		// generate interrupt
		outb_p(0, BASEPORT);
		outb_p(255, BASEPORT);
		timenew = rtai_rdtsc();
		timediff = timenew - timeold;
//		printk("timer=%lld\n", timediff);
		timeold = timenew;
	}
	adeos_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
}

static int write_data(struct file *file, const char *buffer,
                      unsigned long count, void *data) {
	char buf[10];
	int i;
	for (i=0; i<10; i++) buf[i] = 0;
	if (count > sizeof(buf))
		return -EINVAL;
	if (copy_from_user(buf, buffer, count))
		return -EFAULT;
	printk("write_data: %.9s\n", buf);
	return count;
}

// read_proc: read from circular buffer (FIFO)
// Concept from: LRTBF http://www.opersys.com/lrtbf/
static int read_proc (char *page, char **start, off_t offset,
			int count, int *eof, void *data) {
	// offset: 0 if fresh read ; !=0 if *eof was not set to 1 in previous read;
	//         if *eof was 0 previously, there is still old data available
	// count: want to read "count" bytes
	int len = 0;
	*start = page;
	*eof = 0;

	if (whichbuf == 0) {
#ifdef DEBUGITREAD
		printk("READING from buf read_pos=%d write_pos=%d\n", read_pos, write_pos);
#endif
		while (read_pos != write_pos && len <= (count - MAX_STR_LEN )) {
			// len <= (count - MAX_STR_LEN ) :
			//    -> assure that we want to read at least MAX_STR_LEN bytes
			//       MAX_STR_LEN = length of full single data record
			//    stop reading if we have written a max. of count bytes -or-
			//       there is no data in the buffer (read_pos != write_pos)
			memcpy((*start)+len, &buf[read_pos], MAX_STR_LEN);
			len = len + MAX_STR_LEN;
			inc_read_pos();
		}
		if (read_pos == write_pos) {
			*eof = 1; // we have read all data (whole file) -> signal EOF
			// buffer is read -> reset positions
			write_pos = 0;
			read_pos = 0;
		}
	} else {
#ifdef DEBUGITREAD
		printk("READING from buf2 read_pos2=%d write_pos2=%d\n", read_pos2, write_pos2);
#endif
		while (read_pos2 != write_pos2 && len <= (count - MAX_STR_LEN )) {
			memcpy((*start)+len, &buf2[read_pos2], MAX_STR_LEN);
			len = len + MAX_STR_LEN;
			inc_read_pos2();
		}
		if (read_pos2 == write_pos2) {
			*eof = 1; // we have read all data (whole file) -> signal EOF
			// buffer is read -> reset positions
			write_pos2 = 0;
			read_pos2 = 0;
		}
	}
	
	return len;
}

void domain_entry (int iflag) {
	printk("Domain %s started.\n",adp_current->name);

	if (iflag) {

#ifdef APIC_TIMER
		adeos_virtualize_irq(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
		adeos_get_sysinfo(&sys_info);
		adeos_virtualize_irq(sys_info.archdep.tmirq,
			&timer_tick, NULL, IPIPE_DYNAMIC_MASK);
#ifdef PRINTINFO
		printk("USING IRQ 0\n");
#endif
#endif

		// parallel port irq
		adeos_virtualize_irq(PAR_INT,&handler,NULL,IPIPE_DYNAMIC_MASK);
		//set port to interrupt mode; pins are output
		outb_p(0x10, BASEPORT + 2); 
		adeos_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK);
	}

	proc_parint = proc_mkdir("parint", 0);
	if (!proc_parint) {
		printk (KERN_ERR "cannot create /proc/parint\n");
		noproc = 1;
	}

	proc_parint_data = create_proc_read_entry ( "parint/data",
							 0,
							 NULL,
							 read_proc,
							 (void *)&read_proc_buf_pos );

	if (!proc_parint_data) {
		printk (KERN_ERR "cannot create /proc/parint/data\n");
		remove_proc_entry("parint", 0);
		noproc = 1;
	} else {
		proc_parint_data->write_proc = write_data;
	}

	for (;;)
		// This domain's idle loop
		adeos_suspend_domain(); // control back to ADEOS
}
        
static int __init mod_init (void) {
	adattr_t attr;
	attr.name = "TestDomain";
	attr.domid = 1;             // Adeos Domain ID: >0
	attr.entry = &domain_entry;
	attr.estacksz = 0;  // Adeos chooses a reasonable stack size
	attr.priority = ADEOS_ROOT_PRI + 1;
	attr.dswitch = NULL;    // Domain switch hook - always a C routine
	return adeos_register_domain(&this_domain,&attr);
}

static void __exit mod_exit (void) {
#ifdef APIC_TIMER
	rtai_setup_periodic_apic(APIC_ICOUNT, LOCAL_TIMER_VECTOR);
#endif
	if (noproc == 0) {
		remove_proc_entry("parint/data", 0);
		remove_proc_entry("parint", 0);
	}
	adeos_unregister_domain(&this_domain);
}

module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");

user.c
/*
	Simple kernel module with circular buffer (FIFO)
	User space application
	(C) 2005 www.captain.at
	USAGE: see doublefifo.c
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>

#define BUFSIZE 1024*100
#define MAXDATALEN (sizeof(int))

// If defined, int values from kernel space are converted into
//   human readable format before writing to disk
#define CONVERTVALUES

int end = 0;
void clean_exit(int dummy) { end = 1; }

main() {
	int fd, fd2, len, wlen;
	int receive[BUFSIZE];
	char writestr[32*BUFSIZE];

	int tmp = sizeof(int);
	int tmp2, i;
	
	signal(SIGTERM, clean_exit);	
	signal(SIGINT, clean_exit);	

	fd = open("/proc/parint/data", O_RDWR);
	fd2 = open("mydata", O_RDWR | O_CREAT, 0600);
	if( fd == -1) {
		printf("open error...\n");
		exit(0);
	}
	if( fd2 == -1) {
		printf("open error2...\n");
		exit(0);
	}

	while(!end) {
		len = read(fd, receive, BUFSIZE * MAXDATALEN);
#ifdef CONVERTVALUES
		// convert buffer values into readable format
		wlen = 0;
		for( i = 0; i < (len/tmp); i = i + 1) {
			tmp2 = (int)receive[i];
			wlen += sprintf (writestr+wlen, "%d\n", tmp2);
		}
		write(fd2, writestr, wlen);
#else
		write(fd2, writestr, len);
#endif
		usleep(1); // avoid 100% CPU usage
	}

	close(fd);
	close(fd2);
}

Makefile
UNAME := $(shell uname -r)
KERNEL26 := 2.6
KERNELVERSION := $(findstring $(KERNEL26),$(UNAME))

all:: user

ifeq ($(KERNELVERSION),2.6)

obj-m	:= doublefifo.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	:= doublefifo
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

user: user.c
	gcc -o $@ $<

clean::
	$(RM) .doublefifo* *.cmd *.o *.ko *.mod.c mydata user
	$(RM) -R .tmp*

.PHONY: clean
Last-Modified: Thu, 14 Sep 2006 18:26:50 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