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