Dear Xenomai-Users / Developers,

I have finished my work on a parallelport module for measuring external
interrupt latencies. It is based on another kernel-module working on
plain linux. Interrupt latencies are measured via triggering an
interrupt over a connection between data pin 7 and the ACK-pin.

The kernel module loads fine without any problems. When starting the
User-space task the system freezes without any output in the kernel log.
 I was trying to solve the problem for several days now without any
success. I would appreciate, if somebody would have a short look on it.
Maybe its just a small problem which can be fixed easily.

The kernel-module as well as the user-space task are attached.

Thanks in advance and regards,
Markus Franke

PS.: I am aware of the fact that there is already a testcase in the
testsuite for measuring external interrupt latencies. Nevertheless, I
would like to gain experiences in developing with Xenomai and I want to
know why the attached code doesn't work. I will also work with the
testcases in the testsuite soon.
/* This kernel module is strongly based on Thomas Wedemann's module working on top of plain linux */

#include <linux/module.h>
#include <native/intr.h>
#include <native/sem.h>
#include <native/timer.h>
#include <asm/io.h>
#include <asm/timex.h> // cpu_khz
#include "../include/intlat_ioctl.h"

/* parameters for the parallel port... */
#define SPPDATAPORT         0x378
#define SPPSTATUSPORT       (SPPDATAPORT + 1)
#define SPPCONTROLPORT      (SPPDATAPORT + 2)
#define SSPINTERRUPTENABLE  0x10
#define INTR_NR	7

/* the character device */
#define INTLAT_DEV_MAJOR    241
#define INTLAT_DEV_NAME     "parport_lat"

// write some debug messages in /var/log/kern.log
#define DEBUG

#ifndef CONFIG_X86_TSC
# error This module relies on the Time Stamp Counter! Please enable CONFIG_X86_TSC.
#endif

/* we have our own rdtsc... */
#undef rdtsc
/* read the timestamp counter's low 32 bits into <low> */
#define rdtsc(low)    \
	__asm__ __volatile__(           \
		"xorl %%eax,%%eax\n"        \
		"cpuid\n"                   \
		"rdtsc\n"                   \
		"mov %%eax, %0\n"           \
		: "=m"(low)                 \
		:                           \
		: "eax","ebx","ecx","edx"   \
	)

RT_INTR intr;

static atomic_t is_avail = ATOMIC_INIT(1);

// -1, because when installing the module an interrupt is fired which we don't want to take into account
static atomic_t interruptcount = ATOMIC_INIT(-1); 


static unsigned long rdtscoverhead=0,outboverhead=0;
static unsigned long t_start,t_end;
static DECLARE_WAIT_QUEUE_HEAD(intlatpar_queue);

/* semaphore to guarantee that only one process interrupts at a time */
DECLARE_MUTEX(sem);

/* measure the time of outb() */
unsigned long getoutboverhead(void) {
    unsigned long t1,t2;
    
    local_irq_disable();
    rdtsc(t1);
    outb(0x00,SPPDATAPORT);
    outb(0xff,SPPDATAPORT);
    outb(0x00,SPPDATAPORT);
    outb(0xff,SPPDATAPORT);
    outb(0x00,SPPDATAPORT);
    rdtsc(t2);
    local_irq_enable();
    
    return (t2-t1-rdtscoverhead)/5;
}
    
/* get the overhead from a call to "cpuid" before a rdtsc */
unsigned long getrdtscoverhead(void) {
    unsigned long t[4],overhead_3rd,overhead_4th;

    local_irq_disable();
    /* see "Using the RDTSC Instruction for Performance Monitoring" for more... */
    asm (
	 "xorl %%eax,%%eax\n"	    /* run #1 */
         "cpuid\n"
	 "rdtsc\n"
	 "mov %%eax, %0\n"
	 "xorl %%eax,%%eax\n"
	 "cpuid\n"
	 "rdtsc\n"

	 "xorl %%eax,%%eax\n"	    /* run #2 */
	 "cpuid\n"
	 "rdtsc\n"
	 "mov %%eax, %0\n"
	 "xorl %%eax,%%eax\n"
	 "cpuid\n"
	 "rdtsc\n"

	 "xorl %%eax,%%eax\n"	    /* run #3 */
	 "cpuid\n"
	 "rdtsc\n"
	 "mov %%eax, %0\n"
	 "xorl %%eax,%%eax\n"
	 "cpuid\n"
	 "rdtsc\n"
	 "mov %%eax, %1\n"
	 
	 "xorl %%eax,%%eax\n"	    /* run #4 */
	 "cpuid\n"
	 "rdtsc\n"
	 "mov %%eax, %2\n"
	 "xorl %%eax,%%eax\n"
	 "cpuid\n"
	 "rdtsc\n"
	 "mov %%eax, %3\n"
	:   "=m"(t[0]),"=m"(t[1]),"=m"(t[2]),"=m"(t[3])	/* output */
	:						/* no input */
	:   "eax","ebx","ecx","edx"		 	/* clobbered registers */
    );
    local_irq_enable();

    overhead_3rd=t[1]-t[0];
    overhead_4th=t[3]-t[2];

    /* return minimum of both */
    return (overhead_3rd < overhead_4th ? overhead_3rd : overhead_4th);
}

/* called when a process tries to open the device file */
static int latdev_open(struct inode *inode, struct file *file) 
{
	if(!atomic_dec_and_test(&is_avail))
		return -EBUSY;
	try_module_get(THIS_MODULE);
	return 0;
}

/* called when a process closes the device file. */
static int latdev_release(struct inode *inode, struct file *file) 
{
	atomic_inc(&is_avail);
	module_put(THIS_MODULE);
	return 0;
}

/* character dev read() handler */
static ssize_t latdev_read(struct file *filep, char *buf, size_t count, loff_t *ppos) 
{	
	unsigned long lat = 0;
	unsigned long err;
	static volatile int lastint = 0;
	
#ifdef DEBUG
	printk(KERN_INFO "parport_latency: callback read\n");
#endif

	// some value checking
	if(!count)
		return 0;
	if(count < sizeof(unsigned long))
		return -EINVAL;

#ifdef DEBUG
	// sanity check: if the irq line is still high (=> the interrupt handler has not been called)
	if(inb(SPPDATAPORT)==0x80) {
		printk(KERN_WARNING "parport_latency: latdev_read(): data line is still high!\n");
	}
#endif
	
#ifdef DEBUG	
	printk(KERN_INFO "parport_latency: before interrupt generation\n");
#endif
	
	// generate interrupt
	local_irq_disable();
	outb(0x80,SPPDATAPORT);
	rdtsc(t_start);
	local_irq_enable();

#ifdef DEBUG
	printk(KERN_INFO "parport_latency: after interrupt generation\n");
#endif
	
	// wait for isr to produce t_end
	err = wait_event_interruptible(intlatpar_queue, lastint == atomic_read(&interruptcount) - 1 );

#ifdef DEBUG
	if(err == 0)
		
		printk(KERN_INFO "parport_latency: woken up after isr() has fired\n");
	else
	{
		printk(KERN_INFO "parport_latency: wait_event_interruptible has been interrupted\n");
		return err;
	}
#else
	if(err != 0)
	{
		printk(KERN_INFO "parport_latency: wait_event_interruptible has been interrupted\n");
		return err;
	}
#endif

	lastint++;
	 
	lat = t_end - t_start - rdtscoverhead - outboverhead;
	
#ifdef DEBUG
	printk(KERN_INFO "parport_latency: diff = %lu\n", lat);
#endif
	
	err = put_user(lat,(unsigned long *)buf);
	if(err != 0)
	{
		printk(KERN_INFO "Error when copying data to user-space, error = %lu\n", err);
		outb(0x00,SPPDATAPORT);
		return err;
	}

	return 0;
}

int latdev_ioctl(struct inode *inode, struct file *filep, unsigned int cmd, unsigned long arg) 
{
	unsigned long i;
	switch(cmd) {
		case INTLAT_GETFREQ:
		    	i=(unsigned long)cpu_khz * 1000;    /* khz to hz, integer -> ~4GHz max, sufficent for now. */
		    	return (copy_to_user((unsigned long __user *)arg,&i,sizeof(unsigned long))) ? -EFAULT : 0;
		default:
			return -EINVAL;
			break;
	 }
	return 0;
}


// ISR
int parport_isr(xnintr_t* cookie)
{
	rdtsc(t_end);
	
	outb(0x00,SPPDATAPORT);
	
#ifdef DEBUG
	printk(KERN_INFO "parport_latency: Interrupt fired!!!\n");
	printk(KERN_INFO "parport_latency: interruptcount before = %d!!!\n",atomic_read(&interruptcount));
#endif
	
	atomic_inc(&interruptcount);

#ifdef DEBUG
	printk(KERN_INFO "parport_latency: interruptcount after= %d!!!\n",atomic_read(&interruptcount));
#endif

	wake_up_interruptible(&intlatpar_queue);

#ifdef DEBUG
	printk(KERN_INFO "parport_latency: exit parport_isr()!!!\n");
#endif

	return 0;
}

/* the struct of fileoperations */
static struct file_operations intlat_fops = {
	.read = latdev_read,
	.open = latdev_open,
	.release = latdev_release,
	.ioctl = latdev_ioctl
};


// Initialise module
int parport_init(void)
{
	int err = 0;
	
	printk(KERN_INFO "parport_latency: Initialisation started\n");
	
	/* enable parallel port interrupt generation, probably not needed */
        outb(SSPINTERRUPTENABLE,SPPCONTROLPORT);
	
	err = rt_intr_create(&intr, "parport_latency", INTR_NR, &parport_isr, NULL, 0);
	if(err != 0)
	{
		printk(KERN_INFO "parport_latency: Error while requesting Interrupt %d, error %d\n", INTR_NR, err);
		return -1;
	}

	err = rt_intr_enable(&intr);
	if(err != 0)
	{
		printk(KERN_INFO "parport_latency: Error while enabling Interrupt %d, error %d\n", INTR_NR, err);
		return -1;
	}

	if(register_chrdev(INTLAT_DEV_MAJOR,INTLAT_DEV_NAME,&intlat_fops) < 0) 
	{
		printk(KERN_WARNING "intlat: parport_latency(): register_chrdev() failed.\n");
		rt_intr_disable(&intr);
		return -1;
	}
	
	// get rdtsc() overhead
	rdtscoverhead = getrdtscoverhead();
	
	// get outb() overhead
	outboverhead = getoutboverhead();

	/* the type of cpu_khz has changed */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)
	/* kernel 2.6.12.1 */
	printk(KERN_INFO "parport_latency: initialized (cpu_khz=%lu,rdtsc+cpuid: %lu clktk,"
	   "outb(): %lu clktk / %lu us)\n", cpu_khz, rdtscoverhead,outboverhead,
	   ((100000*outboverhead)/cpu_khz)*10);
#else
	/* works at least with 2.6.14 */
	printk(KERN_INFO "parport_latency: initialized (cpu_khz=%u,rdtsc+cpuid: %lu clktk,"
	   "outb(): %lu clktk / %lu ns)\n", cpu_khz, rdtscoverhead,outboverhead,
	   ((100000*outboverhead)/cpu_khz)*10);            /* (prevent int overflow) */
#endif
		
	return 0;
}

// Cleanup module
void parport_exit(void)
{
	int err = 0;
	
	outb(0x00,SPPDATAPORT); // reset output port

	unregister_chrdev(INTLAT_DEV_MAJOR, INTLAT_DEV_NAME);
	if(err != 0)
		printk(KERN_INFO "parport_latency: Error while unregistering the char-device\n");
	
	err = rt_intr_disable(&intr);
	if(err != 0)
		printk(KERN_INFO "parport_latency: Error while disabling Interrupt %d, error %d\n", INTR_NR, err);

	err = rt_intr_delete(&intr);
	if(err != 0)
		printk(KERN_INFO "parport_latency: Error while deleting Interrupt %d, error %d\n", INTR_NR, err);
	
	printk(KERN_INFO "parport_latency: Module uninstalled\n");
}

module_init(parport_init);
module_exit(parport_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Markus Franke (based on Thomas Wiedemann)");
MODULE_DESCRIPTION("interrupt_latency parport module for Xenomai native skin");
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <limits.h>
#include <native/task.h>
#include <native/timer.h>
#include "../include/intlat_ioctl.h"


#define TEST_TIME_S 5 // measuring interrupts for <x> seconds
#define TEST_INTERVALL_US 100000 // test intervall in us
#define HIST_MAX_LATENCY_NS 100000 // maximum latency in ns to show in histogramm

int finished = 0;
int fd;
unsigned long *hist = NULL;
unsigned int max = 0;
unsigned int min = UINT_MAX;

void sighand(int sig __attribute__ ((unused)))
{
	if (sig == SIGXCPU)
		printf("---!! uh oh, switched to secondary mode !!--\n");
	else
		finished = 1;
}

void parport_task(void* cookie)
{
	unsigned long ov, err, i, loops;
	unsigned long lat = 0;
	unsigned int lat_ns;
	
	err = rt_task_set_periodic(NULL, TM_NOW, rt_timer_ns2ticks(TEST_INTERVALL_US * 1000));
	if (err) {
		fprintf(stderr, "parport_user: failed to set periodic, code %d\n", err);
		return;
	}
	
	loops = (TEST_TIME_S * 1000000) / TEST_INTERVALL_US;

	for(i=0; i<loops; i++)
	{
		err = rt_task_wait_period(&ov);
		if(err) 
		{
			fprintf(stderr, "parport_user: failed to wait for next period, code %d\n", err);
			return;
		}	
	
		if((err = read(fd, &lat, sizeof(lat))) == -1) 
			perror("read");

		lat_ns = rt_timer_tsc2ns(lat);
		
		if(lat_ns > HIST_MAX_LATENCY_NS)
		{
			printf("parport_user: %lu ns is to big for histogramm\n",lat_ns);
			break;
		}
		else
			hist[lat_ns]++;
		
		if(lat_ns > max)
			max = lat_ns;
		if(lat_ns < min)
			min = lat_ns;
	}

	alarm(TM_NOW + 1);
}

int main()
{
	int err;
	unsigned int i;
	char* cdev = "/dev/intlat0";
	RT_TASK task;
	unsigned long freq_hz, histsize=0;
	
	if((fd=open(cdev, O_RDONLY))==-1) {
		perror("open");
		fprintf(stderr,"Use mknod to create the character device <%s> and make sure\n"
		               "the module is inserted and no other process is using this file.\n",cdev);
		exit(1);
	}
	
	signal(SIGINT, sighand);
	signal(SIGTERM, sighand);
	signal(SIGHUP, sighand);
	signal(SIGALRM, sighand);

	if(ioctl(fd,INTLAT_GETFREQ,&freq_hz)==-1) {
		perror("ioctl");
		fprintf(stderr,"Ioctl(INTLAT_GETFREQ) is not supported on the device, please make sure the\n"
			       "character device has been set up correctly and the module is up to date!\n");
		exit(1);
	}
	
	printf("frequency = %lu Hz\n",freq_hz);

	histsize = HIST_MAX_LATENCY_NS;
	printf("histsize = %lu\n",histsize);
	
	if((hist = calloc(histsize, sizeof(unsigned int))) == NULL) 
	{
		perror("calloc");
		fprintf(stderr,"Out of memory? Can't allocate <%lu> bytes!\n",histsize * sizeof(unsigned int));
		exit(1);
	}

	mlockall(MCL_CURRENT | MCL_FUTURE);
	
	err = rt_timer_set_mode(TM_ONESHOT);    /* Force aperiodic timing. */
	if (err) {
		fprintf(stderr, "parport_user: failed to start timer, code %d\n", err);
		return 0;
	}
			 

	err = rt_task_create(&task, "parport_user", 0, T_HIPRIO, T_FPU);
	if (err) {
		fprintf(stderr, "parport_user: failed to create parport user task, code %d\n", err);
                return 0;
        }
	
	err = rt_task_start(&task, &parport_task, NULL); 
	if (err) {
		fprintf(stderr, "parport_user: failed to create parport user task, code %d\n", err);
		return 0;
	}
	
	while(!finished)
		pause();

	for(i=min;i<=max;i++) {
		printf("%6lu  %6lu \n",i,hist[i]);
	}
	
	rt_task_delete(&task);
	free(hist);
	close(fd);

	return err;
}

begin:vcard
fn:Markus Franke
n:Franke;Markus
adr;quoted-printable:;;Vettersstra=C3=9Fe 64/722;Chemnitz;Saxony;09126;Germany
email;internet:[EMAIL PROTECTED]
x-mozilla-html:FALSE
url:http://www.tu-chemnitz.de/~franm
version:2.1
end:vcard

_______________________________________________
Xenomai-help mailing list
[email protected]
https://mail.gna.org/listinfo/xenomai-help

Reply via email to