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      


UPDATE: Click here if you have problems with the parallel port interrupt

ADEOS IPIPE Linux Hard Real Time - Device Driver with MMAP


Here we have an ADEOS IPIPE device driver example that has double buffering and MMAP-ing. Compile with the Makefile. You can decide with "#define READBLOCKING" if you want to poll for new data from user-space with ioctl or if you want to use a blocking read function. Furthermore you can decide with "#define DO_VIRQ" if you want to use a virtual IRQ to unblock the read operation, or use the parallel port interrupt to unblock.


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

Insert the HRT module (for kernel 2.4) with
# insmod ./skeleton.o
-or for kernel 2.6-
# insmod ./skeleton.ko
start the user process (which will write some data to "dump.txt") in another console
# ./user
and stimulate some parallel port interrupts (again in another console) with
# ./toogle
Check the kernel log now - you see some debug messages, e.g. when the buffer is switched etc.

Before all that you must create the device entry in /dev with
# mknod -m 666 /dev/skeleton c 240 0

Developed and tested with kernel 2.4.32 (adeos-ipipe-2.4.32-i386-1.1-02.patch). Also tested on kernel 2.6.15 with the IPIPE 1.1-03 patch.


NOTE: If you test on kernels 2.4 and 2.6, make sure you do a "make clean" and everything should compile well.

skeleton.c
// ADEOS-IPIPE/Linux Device Driver Template/Skeleton with mmap
// Kernel Module

// # mknod -m 666 /dev/skeleton c 240 0

#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/slab.h>
#include "inc.h"

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#include <linux/wrapper.h>
#endif

static unsigned int counter = 0;
static char string [128];

static int which_buffer = 0;
static int buffer1_read = 1;
static int buffer2_read = 1;

unsigned long virt_addr;
static unsigned long inter_domain_irq; /* VIRQ used as interdomain irq */

static struct ipipe_domain this_domain;

DECLARE_WAIT_QUEUE_HEAD(skeleton_wait);

#ifdef READBLOCKING
static int data_not_ready = 1;
#else
static int data_not_ready = 0;
#endif

// open function - called when the "file" /dev/skeleton is opened in userspace
static int skeleton_open (struct inode *inode, struct file *file) {
	printk("skeleton_open\n");
	// we could do some checking on the flags supplied by "open"
	// i.e. O_NONBLOCK
	// -> set some flag to disable interruptible_sleep_on in skeleton_read
	return 0;
}

// close function - called when the "file" /dev/skeleton is closed in userspace  
static int skeleton_release (struct inode *inode, struct file *file) {
	printk("skeleton_release\n");
	return 0;
}

// read function called when from /dev/skeleton is read
static ssize_t skeleton_read (struct file *file, char *buf,
		size_t count, loff_t *ppos) {
	int err;
	
	// check if we have data - if not, sleep
	// wake up in interrupt_handler
	while (data_not_ready) {
		interruptible_sleep_on(&skeleton_wait);
	}
#ifdef DEBUGIT
			printk("MODULE READ UNBLOCKED data_not_ready=%d\n", data_not_ready);
#endif
#ifdef READBLOCKING
	data_not_ready = 1;
#endif

//	err = copy_to_user(buf,string,counter);
	err = copy_to_user(buf, &which_buffer, sizeof(int) );
	if (err != 0)
		return -EFAULT;
	return sizeof(int);
}

// write function called when to /dev/skeleton is written
static ssize_t skeleton_write (struct file *file, const char *buf,
		size_t count, loff_t *ppos) {
	int err;
	err = copy_from_user(string,buf,count);
	if (err != 0)
		return -EFAULT;
	counter += count;
	return count;
}

// ioctl - I/O control
static int skeleton_ioctl(struct inode *inode, struct file *file,
		unsigned int cmd, unsigned long arg) {
	int retval = 0;
	switch ( cmd ) {
		case SWITCHBUFFER:/* for writing data to arg */
			if (copy_from_user(&which_buffer, (int *)arg, sizeof(int)))
			return -EFAULT;
			break;
		case WHICHBUFFER:/* for reading data from arg */
			if (copy_to_user((int *)arg, &which_buffer, sizeof(int)))
			return -EFAULT;
			break;
		case BUFFER1READ:
			if (copy_from_user(&buffer1_read, (int *)arg, sizeof(int)))
			return -EFAULT;
			break;
		case BUFFER2READ:
			if (copy_from_user(&buffer2_read, (int *)arg, sizeof(int)))
			return -EFAULT;
			break;
		case UNBLOCKREAD:
			data_not_ready = 0; // unblock skeleton_read
			//wake_up_interruptible(&skeleton_wait);
			break;
		default:
			retval = -EINVAL;
	}
	return retval;
}


#ifndef VMALLOC_VMADDR
#define VMALLOC_VMADDR(x) ((unsigned long)(x))
#endif

// From: http://www.scs.ch/~frey/linux/memorymap.html
volatile void *virt_to_kseg(volatile void *address) {
	pgd_t *pgd; pmd_t *pmd; pte_t *ptep, pte;
	unsigned long va, ret = 0UL;
	va=VMALLOC_VMADDR((unsigned long)address);
	/* get the page directory. Use the kernel memory map. */
	pgd = pgd_offset_k(va);
	/* check whether we found an entry */
	if (!pgd_none(*pgd)) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
		/* get the page middle directory */
		pmd = pmd_offset(pgd, va);
#else
		// I'm not sure if we need this, or the line for 2.4
		//    above will work reliably too
		// If you know, please email me :-)
		pud_t *pud = pud_offset(pgd, va);		
		pmd = pmd_offset(pud, va);
#endif
		/* check whether we found an entry */
		if (!pmd_none(*pmd)) {
			/* get a pointer to the page table entry */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
			ptep = pte_offset(pmd, va);
#else
			ptep = pte_offset_map(pmd, va);
#endif
			pte = *ptep;
			/* check for a valid page */
			if (pte_present(pte)) {
				/* get the address the page is refering to */
				ret = (unsigned long)page_address(pte_page(pte));
				/* add the offset within the page to the page address */
				ret |= (va & (PAGE_SIZE -1));
			}
		}
	}
	return((volatile void *)ret);
}

static int skeleton_mmap(struct file * filp, struct vm_area_struct * vma) {
	int ret;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	ret = remap_page_range(vma->vm_start,
			virt_to_phys((void*)((unsigned long)buffer_area)),
			vma->vm_end-vma->vm_start,
			PAGE_SHARED);
#else
        ret = remap_pfn_range(vma,
               vma->vm_start,
               virt_to_phys((void*)((unsigned long)buffer_area)) >> PAGE_SHIFT,
               vma->vm_end-vma->vm_start,
               PAGE_SHARED);
#endif
#ifdef DEBUGIT
		printk("MMAP buffer_area\n");
#endif

	if(ret != 0) {
		return -EAGAIN;
	}
	return 0;
}

// define which file operations are supported
struct file_operations skeleton_fops = {
	.owner	=	THIS_MODULE,
	.llseek	=	NULL,
	.read		=	skeleton_read,
	.write	=	skeleton_write,
	.readdir	=	NULL,
	.poll		=	NULL,
	.ioctl	=	skeleton_ioctl,
	.mmap		=	skeleton_mmap,
	.open		=	skeleton_open,
	.flush	=	NULL,
	.release	=	skeleton_release,
	.fsync	=	NULL,
	.fasync	=	NULL,
	.lock		=	NULL,
	.readv	=	NULL,
	.writev	=	NULL,
};

int write_pos = 0;
unsigned int parcounter = 0;

void handler(unsigned irq) {
	unsigned long flags;
	flags = ipipe_critical_enter (NULL);
	parcounter++;
	if (which_buffer == 0) {
		buffer_ptr[write_pos] = parcounter;
		write_pos++;
		if (write_pos >= NUMBUFFER) {
			buffer1_read = 0;
			which_buffer = 1;
#ifdef READBLOCKING
			data_not_ready = 0; // unblock skeleton_read
#ifdef DO_VIRQ
			// we trigger the virtual interrupt, so that the
			//   ipipe handler calls wake_up_interruptible
			//   and wakes up the blocking chrdev read
			// wake_up_interruptible can break hard real time
			//   but since it is called from another IRQ handler
			//   it is OK
			ipipe_trigger_irq(inter_domain_irq);
#else
			// propagate the IRQ to the linux domain, which will
			//   wake up the blocking chrdev read with wake_up_interruptible
			ipipe_propagate_irq(irq);
#endif
#endif
#ifdef DEBUGIT
			printk("SWITCHED TO buffer2_ptr NUMBUFFER=%d write_pos=%d\n", 
				NUMBUFFER, write_pos);
			if (buffer2_read == 0) {
				printk("DATA LOSS\n");
			}
#endif
		}
	} else {
		buffer_ptr[write_pos] = parcounter;
		write_pos++;
		if (write_pos >= NUMDATA) {
			buffer2_read = 0;
			write_pos = 0;
			which_buffer = 0;
#ifdef READBLOCKING
			data_not_ready = 0; // unblock skeleton_read
#ifdef DO_VIRQ
			ipipe_trigger_irq(inter_domain_irq);
#else
			ipipe_propagate_irq(irq);
#endif
#endif
#ifdef DEBUGIT
			printk("SWITCHED TO buffer_ptr NUMDATA=%d write_pos=%d\n", 
				NUMDATA, write_pos);
			if (buffer1_read == 0) {
				printk("DATA LOSS\n");
			}
#endif
		}

	}	
	ipipe_critical_exit (flags);
#ifdef DO_VIRQ
	// virtual interrupt: pass the parallel port interrupt to the linux domain
	ipipe_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK);
	ipipe_propagate_irq(irq);
#else 
	// parallel port int: acknowledge the IRQ, but do not pass it to the
	//   linux domain. We trigger the linux domain par-int above only
	//   when data is available for read
	// Since linux does not see much parallel port interrupts, the virtual
	//   interrupt method above is better
	ipipe_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK | IPIPE_HANDLE_MASK);
#endif
}

#ifdef DO_VIRQ
void wake_up_handler(unsigned irq) {
#else
static int wake_up_handler(void) {
#endif
	wake_up_interruptible(&skeleton_wait);
	return IRQ_HANDLED;
}

static void domain_entry (void) {
	printk("Domain %s started.\n",ipipe_current_domain->name);
#ifdef DO_VIRQ
	// we use a dynamic virtual interrupt
	inter_domain_irq = ipipe_alloc_virq();
	printk("Using VIRQ %d\n", inter_domain_irq);
	// The syscall "wake_up_interruptible" in "wake_up_handler" will make the
	//    "wake_up_handler" to run in soft-real-time, which is OK, but it
	//    doesn't affect and other hard-real-time interrupt handlers!
	ipipe_virtualize_irq(ipipe_current_domain, inter_domain_irq, 
		(ipipe_irq_handler_t)&wake_up_handler,NULL,NULL,IPIPE_DYNAMIC_MASK);
#endif
	// parallel port irq
	ipipe_virtualize_irq(ipipe_current_domain, PAR_INT, 
		(ipipe_irq_handler_t)&handler,NULL,NULL,IPIPE_DYNAMIC_MASK);
	//set port to interrupt mode; pins are output
	outb_p(0x10, BASEPORT + 2);
#ifdef DO_VIRQ
	// pass interrupts to linux
	ipipe_control_irq(PAR_INT,0,IPIPE_ENABLE_MASK);
#else
	// hide interrupts from linux
	ipipe_control_irq(PAR_INT,0,IPIPE_HANDLE_MASK);
#endif

}

static int __init skeleton_init_module (void) {
    struct ipipe_domain_attr attr;
    ipipe_init_attr (&attr);
    attr.name		= "TestDomain";
    attr.priority	= IPIPE_ROOT_PRIO + 1;
    attr.entry		= &domain_entry;
    
	int i;
	printk("initializing module\n");
	
	i = register_chrdev (SKELETON_MAJOR, SKELETON_NAME, &skeleton_fops);
	if (i != 0) return - EIO;

	// reserve memory with kmalloc - Allocating Memory in the Kernel
	buffer_ptr = (unsigned int *)kmalloc(BUFFERSIZE + 2 * PAGE_SIZE, GFP_KERNEL);
	if (!buffer_ptr) { printk("kmalloc failed\n"); return 0; }
	buffer_area = (unsigned int *)(((unsigned long)buffer_ptr + PAGE_SIZE -1) & PAGE_MASK);
	for (virt_addr=(unsigned long)buffer_area; virt_addr < (unsigned long)buffer_area + 
		BUFFERSIZE; virt_addr+=PAGE_SIZE) {
			// reserve all pages to make them remapable
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
			mem_map_reserve(virt_to_page(virt_addr));
#else
			SetPageReserved(virt_to_page(virt_addr));
#endif
	}
#ifdef DEBUGIT
	printk(" buffer_area at 0x%p (phys 0x%lx)\n", buffer_area,
		virt_to_phys((void *)virt_to_kseg(buffer_area)));
#endif

	// fill allocated memory with 0
	for( i = 0; i < (BUFFERSIZE / DATASIZE); i = i + 1) {
		buffer_ptr[i] = 0;
	}
   
#ifdef DO_VIRQ
#else
	// we wake up with a regular linux interrupt
   int ret;
	ret = request_irq(PAR_INT, wake_up_handler, SA_INTERRUPT, "virtualIRQ", NULL);
//	enable_irq(PAR_INT);
#endif
   return ipipe_register_domain(&this_domain,&attr);
}

// close and cleanup module
static void __exit skeleton_cleanup_module (void) {
	printk("cleaning up module\n");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	for (virt_addr=(unsigned long)buffer_area; virt_addr < (unsigned long)buffer_area + 
	BUFFERSIZE; virt_addr+=PAGE_SIZE) {
			// clear all pages
			ClearPageReserved(virt_to_page(virt_addr));
	}
#endif
	kfree(buffer_ptr);
	unregister_chrdev (SKELETON_MAJOR, SKELETON_NAME);

#ifdef DO_VIRQ
	ipipe_free_virq(inter_domain_irq);
#else
	disable_irq(PAR_INT);
	free_irq(PAR_INT, NULL);
#endif	
	ipipe_unregister_domain(&this_domain);
}

module_init(skeleton_init_module);
module_exit(skeleton_cleanup_module);
MODULE_AUTHOR("www.captain.at");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ADEOS-IPIPE/Linux Device Driver Template with MMAP");

user.c
// ADEOS-IPIPE/Linux Device Driver Template/Skeleton with mmap
// Userspace test program

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <signal.h>
#include "inc.h"

int end = 0;
int fd;
int buffer_ack = 1;

void clean_exit(int dummy) {
	end = 1;
	ioctl(fd, UNBLOCKREAD, &buffer_ack);
	printf("WAITING FOR COMPLETION\n");
}

int main(void) {
	int i, fd2, len, wlen, tmp, tmp2;
	char string[] = "Skeleton Kernel Module Test";
	char receive[128];
	char tfn[20];
	int active_buffer = 0;
	int active_buffer_old = 0;
	
	char * mptr;
	char * mptr2;
	size_t size = BUFFERSIZE;
	unsigned int *mybuffer;
	time_t newtime;

	signal(SIGTERM, clean_exit);	
	signal(SIGINT, clean_exit);	

	if (( mybuffer = (unsigned int *) malloc( BUFFERSIZE ) ) == NULL) {
		printf("ERROR ALLOCATING mybuffer\n");
		exit(1);
	}

	fd = open("/dev/skeleton", O_RDWR | O_SYNC);
	if( fd == -1) {
		printf("open error...\n");
		exit(0);
	}

		mptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0);
		if(mptr == MAP_FAILED) {
			printf("mmap() failed\n");
			goto endofprg;
		}
//	   if ((fd2 = open(tfn, O_CREAT | O_TRUNC | O_RDWR, 0755)) < 0)
	   if ((fd2 = open("dump.txt", O_CREAT | O_TRUNC | O_RDWR, 0755)) < 0)
			fprintf(stderr, "File open error.\n");

	while ( !end ) {
#ifdef READBLOCKING
		len = read(fd, &active_buffer, sizeof(int));
		printf("unblocked len=%d active_buffer=%d\n", len, active_buffer);
#else	
		while ( !end && (active_buffer_old == active_buffer) ) {
			ioctl(fd, WHICHBUFFER, &active_buffer);
			usleep(100000);
		}
		active_buffer_old = active_buffer;
#endif

//		newtime =  time(NULL);
//		sprintf(tfn, "dump-%d-%d", newtime, active_buffer);

		if (active_buffer == 1) {
			memcpy(mybuffer, mptr, OFFSET); // read buffer 0
		} else {
			memcpy(mybuffer, mptr + OFFSET, OFFSET);  // read buffer 1
		}
	   if (write(fd2, mybuffer, OFFSET) != OFFSET)
	      fprintf(stderr, "File write error.\n");
		//sync();
		if (active_buffer == 1) {
			ioctl(fd, BUFFER1READ, &buffer_ack);
		} else {
			ioctl(fd, BUFFER2READ, &buffer_ack);
		}
	}
	close(fd2);
endofprg:
	munmap(mptr, size);
	close(fd);
	
}

toggle.c
#include <stdio.h>
#include <stdlib.h>
#include <asm/io.h>
#include <termios.h>
#include <time.h>
#include <signal.h>

#define BASEPORT 0x378
int end = 0;
void clean_exit(int dummy) { end = 1; }

int main(int argc, char **argv) {
	if (ioperm(BASEPORT, 3, 1)) {  	
			// ask for permissions to access the parallel port
			// YOU MUST BE ROOT TO ASK FOR THIS SORT OF PERMISSION, so
			//     don't forget to 'su' yourself ;-)
			// we don't want to interfere with other code
			// usage of ioperm: BASEPORT ... base address we want to use
			//		3 ... the next 3 bytes
			//		1 ... we want the permission
		perror("ioperm open");
		exit(1);
	}
	outb(0x10, BASEPORT + 2); // set to output mode; interrupt enabled
	signal(SIGTERM, clean_exit);	
	signal(SIGINT, clean_exit);
	while (!end) {
		outb(0, BASEPORT);
		usleep(10000);
		outb(255, BASEPORT);
		usleep(10000);
	}
	freeperm(); // tell the system we do not use the port anymore
	exit(0);
}

int freeperm() {
	if (ioperm(BASEPORT, 3, 0)) { 
		// tell the system we do not use the port anymore
		// usage of ioperm: BASEPORT ... base address we want to use
		//					3 ... the next 3 bytes
		//					0 ... free the permission	
		perror("ioperm close");
	}
}

inc.h
#define BUFFERSIZE 1*1024
//#define BUFFERSIZE 120*1024

#define DO_VIRQ

#define DEBUGIT
#define READBLOCKING

#define SKELETON_MAJOR 240
#define SKELETON_NAME "skeleton"
#define BASEPORT 0x378
#define PAR_INT 7

#define DATASIZE (sizeof(unsigned int))
#define OFFSET (BUFFERSIZE / 2)
#define NUMDATA (BUFFERSIZE / DATASIZE)
#define NUMBUFFER (OFFSET / DATASIZE)

#define SWITCHBUFFER 1
#define WHICHBUFFER 2
#define BUFFER1READ 3
#define BUFFER2READ 4
#define UNBLOCKREAD 5

unsigned int *buffer_ptr;
unsigned int *buffer_area;



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

all:: user toggle

ifeq ($(KERNELVERSION),2.6)

obj-m	:= skeleton.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	:= skeleton
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 $@ $<

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

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

Last-Modified: Tue, 17 Jun 2008 18:26:29 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