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 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

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