ADEOS IPIPE Linux Hard Real Time Stress Test
Here we have an ADEOS IPIPE stress test that works on 2.6 kernels and the newly
released 2.4 ipipe patch.
This one is basically the same as the Adeos Hard Real Time Parallel Port ISR with Double Buffering FIFO,
but changed for the new ADEOS IPIPE series.
Compile the stuff with "make" - insert the kernel module with "insmod ./muon.ko" or "insmod ./muon.o"
and immediately start "./user" in another shell. Make sure you've created a directory "data" before.
Check the files in "data/" if all works well.
This example is now used as stress test example - it was originally thought as hard real time sound
sampling application for use in radio astronomy. With some additionaly code it can and will
become a hard real time sound sampling application - see
AD976 parallel port
hardware. Personally I'm gonna use this application as RadioJove
Jupiter Radio Astronomy data acquisition software.
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.
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.32 (adeos-ipipe-2.4.32-i386-1.0-00.patch). Also tested with kernel 2.6.14.5 (adeos-ipipe-2.6.14-i386-1.1-00.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.
muon.c
/*
ADEOS IPIPE kernel module with circular buffer (FIFO)
Module (C) 2005 www.captain.at
FIFO (C) 2005 www.opersys.com (LRTBF http://www.opersys.com/lrtbf/)
*/
#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 APIC_TIMER // if this is present, use our own APIC frequency
//#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 1000000000
#define BUFFER_SIZE 128000
//#define BUFFER_SIZE 1280000
#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
struct proc_dir_entry *proc_muon;
struct proc_dir_entry *proc_muon_data;
long long parcounter = 0;
long long timercounter = 0;
#define SAMPLE_FREQUENCY 44000
#define mytick FREQ_APIC / SAMPLE_FREQUENCY
#define MAX_STR_LEN (sizeof(long long))
long long buf[BUFFER_SIZE];
long long buf2[BUFFER_SIZE];
int noproc = 0;
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");
printk("DATA LOSS: 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);
}
#ifdef DEBUGITSWITCH
printk("switched to buf2 read_pos=%d write_pos=%d read_pos2=%d write_pos2=%d parcnt=%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 LOSS2: SWITCHING TO BUF BEFORE BUF WAS READ COMPLETELY\n");
printk("DATA LOSS2: 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);
}
#ifdef DEBUGITSWITCH
printk("switched to buf read_pos=%d write_pos=%d read_pos2=%d write_pos2=%d parcnt=%d\n",
read_pos, write_pos, read_pos2, write_pos2, (int)parcounter);
#endif
}
return write_pos2;
}
void handler(unsigned irq) {
unsigned long flags;
flags = ipipe_critical_enter (NULL);
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
buf[write_pos] = (long long)parcounter;
inc_write_pos();
} else {
#ifdef DEBUGIT
printk("writing to buf2 read_pos2=%d write_pos2=%d parcounter=%d\n",
read_pos2, write_pos2, (int)parcounter);
#endif
buf2[write_pos2] = (long long)parcounter;
inc_write_pos2();
}
ipipe_critical_exit (flags);
ipipe_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK);
ipipe_propagate_irq(irq);
}
void timer_tick (unsigned irq) {
timercounter++;
// if (timercounter < MAX_INTS) {
// generate interrupt
outb_p(0, BASEPORT);
outb_p(255, BASEPORT);
// }
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
}
// 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;
}
static void domain_entry (int iflag) {
printk("Domain %s started.\n",ipipe_current_domain->name); //
if (iflag) {
#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
// 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);
}
proc_muon = proc_mkdir("muon", 0);
if (!proc_muon) {
printk (KERN_ERR "cannot create /proc/muon\n");
noproc = 1;
}
proc_muon_data = create_proc_read_entry ( "muon/data",
0,
NULL,
read_proc,
(void *)&read_proc_buf_pos );
if (!proc_muon_data) {
printk (KERN_ERR "cannot create /proc/muon/data\n");
remove_proc_entry("muon", 0);
noproc = 1;
}
/*
for (;;)
// This domain's idle loop
ipipe_suspend_domain(); // control back to ADEOS
*/
}
static int __init mod_init (void) {
struct ipipe_domain_attr attr;
ipipe_init_attr (&attr);
attr.name = "TestDomain";
attr.priority = IPIPE_ROOT_PRIO + 1;
attr.entry = &domain_entry;
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
if (noproc == 0) {
remove_proc_entry("muon/data", 0);
remove_proc_entry("muon", 0);
}
ipipe_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
*/
#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 128000
#define MAXDATALEN (sizeof(long long))
// 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, cnt;
long long receive[BUFSIZE];
char writestr[32*BUFSIZE];
char teststr[BUFSIZE];
char fd2name[16];
char tfn[20];
int i;
int myx = 0;
long long tmp2;
signal(SIGTERM, clean_exit);
signal(SIGINT, clean_exit);
time_t captime;
time_t newtime;
fd = open("/proc/muon/data", O_RDWR);
fd2 = open("data/mydata", O_RDWR | O_CREAT, 0600);
if( fd == -1) {
printf("open error...\n");
exit(0);
}
if( fd2 == -1) {
printf("open error2...\n");
exit(0);
}
cnt = 0;
captime = time(NULL);
while(!end) {
len = read(fd, receive, BUFSIZE * MAXDATALEN);
#ifdef CONVERTVALUES
// convert buffer values into readable format
wlen = 0;
for( i = 0; i < (len/MAXDATALEN); i = i + 1) {
tmp2 = (long long)receive[i];
wlen += sprintf (writestr+wlen, "%lld\n", tmp2);
if (i == 0) {
printf("WE HAVE %lld\n", tmp2);
}
}
write(fd2, writestr, wlen);
#else
write(fd2, writestr, len);
#endif
usleep(1); // avoid 100% CPU usage
cnt++;
newtime = time(NULL);
if ((newtime - captime) > 90) {
printf("time=%d\n", newtime);
sync();
close(fd2);
sprintf(tfn, "data/d-%d", newtime);
fd2 = open(tfn, O_RDWR | O_CREAT, 0600);
write(fd2, "CAPTAIN\n", 8);
captime = time(NULL);
if( fd2 == -1) {
printf("ROTATION ERROR\n");
}
close(fd);
fd = open("/proc/muon/data", O_RDWR);
}
}
close(fd);
close(fd2);
}
Makefile
UNAME := $(shell uname -r)
KERNEL26 := 2.6
KERNELVERSION := $(findstring $(KERNEL26),$(UNAME))
all:: user
ifeq ($(KERNELVERSION),2.6)
obj-m := muon.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 := muon
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) .muon* *.cmd *.o *.ko *.mod.c mydata user
$(RM) -R .tmp*
.PHONY: clean
Last-Modified: Thu, 14 Sep 2006 18:26:52 GMT