Due to popular demand (4 requests so far!), I'm posting a simple kernel
module which catches interrupts for delivery to user space programs. The
authors of this module are most definitely novice kernel hackers, so a
serious critique of this module would be appreciated. I have also
some pseudo-code which outlines how our driver talks to the parallel port
using hardware EPP reads and writes. We get about 1MB/s throughput on a
SIIG ISA parallel port card.
This code is released under no specific licence, but most definately with
no warranty whatsoever.
This version is a slight improvement over what I have sent to a couple of
people earlier. If this version gets woken up by a user signal, rather
than by a real interrupt, it returns a -1 on the read. Otherwise it
returns 0.
The kernel module compiles with:
cc -O2 -c PP_irq.c
then: insmod PP_irq irq=5,7
(substitute whatever irq's you want it to catch - they are assigned
in order to minor devices 0,1, etc.
Check dmesg to see if it loaded correctly. We're using 2.2.13.
you'll also need to:
mknod /dev/PP_irq0 c 249 0
mknod /dev/PP_irq1 c 249 1
these are the device files it uses to communicate with user programs.
To catch interrupts then, you just open the device file and do a read:
fd = open( "/dev/PP_irq0", O_RDONLY );
read( fd, buffer, 1 );
the read blocks until an interrupt comes.
--------------------------------
PP_irq.c
---------------------------------
/* PP_irq.c
*
* Implementation of a simple character device that waits for interrupts.
* The device does not return any useful data, but simply pauses until an
* interrupt is generated on the line specified. This
* essentially allows user space programs to wait for an interrupt.
*
* To avoid difficulties in adding and removing the interrupt handlers
when
* multiple users try to access the device, the modules loads the
interrupt
* handlers at module load time, not when the device is opened. This
means
* that this module occupies the interrupt lines even when it is not in
use.
*
* Copyright Feb. 04, 2000 Scott Nelson and Carl Michal
*
*
*/
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/sched.h> //for interrupt functions
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/malloc.h>
//#include <linux/errno.h>
#include "PP_irq.h"
#define PP_irq_MAX_PORTS 4
/* this is a an array of pointers to wait queues. */
static struct wait_queue* PP_irq_wait_queue[ PP_irq_MAX_PORTS ];
static int PP_major = 249;
for autodetect
static int handler[PP_irq_MAX_PORTS]; // array of interrupts used by
this module
static int num_handlers=0; // the number of interrupt
handlers that have been loaded
/*
* Define the file operations implemented in this module
*/
static struct file_operations PP_irq_fops = {
NULL, //lseek
PP_read,
NULL, //write
NULL, //readdir
NULL, //select
NULL, //ioctl
NULL, //mmap
PP_open,
NULL,
PP_release
};
static int PP_open( struct inode * inode, struct file * flip)
{
if (MINOR( inode->i_rdev ) > num_handlers-1) return ENODEV;
MOD_INC_USE_COUNT;
return 0;
}
static int PP_release( struct inode * inode, struct file * filp)
{
MOD_DEC_USE_COUNT;
return 0;
}
static int PP_read( struct file * file, char *buf, size_t count,loff_t *ppos )
{
unsigned int minor= MINOR (file->f_dentry->d_inode->i_rdev);
// printk("<1>PP_irq: Minor dev %d opened for read\n",minor);
interruptible_sleep_on( &(PP_irq_wait_queue[minor]) );
if( sigismember( ¤t->signal, SIGUSR1 ) )
//if( current->signal )
return -1;
return 1;
}
void PP_interrupt_handler( int irq, void *dev_id, struct pt_regs *regs )
{
int i;
// printk( "<1> PP_interrupt handler invoked on irq %d\n",irq );
for(i=0;i<num_handlers;i++)
if(irq==handler[i]){
wake_up_interruptible( &(PP_irq_wait_queue[i]) );
// printk("<1>PP_irq: that irq belongs to minor dev %d\n",i);
}
}
int PP_load_handler(int irq )
{
return request_irq( irq, PP_interrupt_handler, SA_INTERRUPT,
"PP_irq",NULL );
}
void PP_remove_handlers( )
{
int i;
for(i=0;i<num_handlers;i++){
free_irq( handler[i], NULL );
printk( "<1>PP_irq: interrupt handler removed on irq %d\n",handler[i]);
}
return;
}
static int irq[PP_irq_MAX_PORTS] = { [0 ... PP_irq_MAX_PORTS-1] = 0 };
MODULE_PARM(irq, "1-" __MODULE_STRING(PP_irq_MAX_PORTS) "i");
int init_module()
{
int result,i;
result = register_chrdev( PP_major, "PP_irq", &PP_irq_fops ); //register
the device
if( result<0 ){
printk( "<1>PP_irq: couldn't register a major number\n" );
return result;
}
if( PP_major == 0 ) PP_major = result; // dynamic allocation if no
parameter is specified
printk("<1>PP_irq: Registered PP_irq with Major number: %d\n",PP_major);
// Now load interrupt handlers for the irq's requested
printk( "<1> PP_irq: attempting to load handlers on irqs: %d, %d, %d, %d\n"
, irq[0], irq[1], irq[2], irq[3] );
for(i=0; i<PP_irq_MAX_PORTS && irq[i];i++){
result = PP_load_handler(irq[i]);
if(result==0){
handler[num_handlers]=irq[i];
printk("<1>PP_irq: irq %d handler loaded as minor dev: %d\n"
,irq[i],num_handlers);
num_handlers++;
}
else printk("<1>PP_irq: irq %d handler not loaded\n",irq[i]);
}
if (num_handlers == 0){
unregister_chrdev(PP_major,"PP_irq");
printk("<1>PP_irq: No handlers located, giving up\n");
return 1;
}
printk("<1>PP_irq: found %d irqs, loaded %d handlers\n",i,num_handlers);
for(i=0;i<PP_irq_MAX_PORTS;i++) PP_irq_wait_queue[i]=NULL;
printk( "<1>PP_irq: module loaded\n" );
return 0;
}
int cleanup_module()
{
PP_remove_handlers(); //unload the interrupt handlers
unregister_chrdev( PP_major, "PP_irq" ); // unregister device
printk( "<1>PP_irq: module removed\n" );
return 0;
}
--------------------------------------
PP_irq.h
-------------------------------------
/* PP_irq.h
*
* Header file for PP_irq.c
*
*
*
*/
#define __KERNEL__
#define MODULE
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
static int PP_open( struct inode * inode, struct file * flip);
static int PP_release( struct inode * inode, struct file * filp);
static int PP_read( struct file * file, char *buf, size_t count,loff_t *ppos );
void PP_interrupt_handler( int irq, void *dev_id, struct pt_regs *regs );
int PP_load_handler( int irq );
void PP_remove_handlers( );
int init_module();
int cleanup_module();
----------------------------------------------
----------------------------------------------
My understanding is that code which uses inb or outb *must* be compiled
with -O or -O2, and has to be run with root permission.
#include <stdio.h>
#include <sys/io.h>
int main()
{
const unsigned long PORT = 0x278;
const unsigned long EPP_DATA = PORT + 0x04;
const unsigned long EPP_ADDR = PORT + 0x03;
const unsigned long SPP_CTRL = PORT + 0x02;
const unsigned long SPP_STAT = PORT + 0x01;
const unsigned long SPP_DATA = PORT;
//get permission to use the parallel port
i = ioperm( PORT, 8, 1 );
if( i<0 ) {
printf( "Can't get permission to access the port 0x%x\n", PORT );
exit(1);
}
//initialize EPP mode by writing to control port
outb( 0x08, SPP_CTRL );
//clear the EPP timeout bit on the status register
//this code is adapted from parport_pc.c
inb( SPP_STAT );
b=inb( SPP_STAT ); //read in the status register
outb( b & 0xfe, SPP_STAT ); //write bit 0 to 0, leave the rest the same
b = inb( SPP_STAT );
if( b & 0x01) {
printf( "Couldn't reset EPP timeout bit on port 0x%x\n", PORT );
exit(1);
}
// then data writes look like:
outb(char xxx, EPP_DATA ); // xxx is the byte to write to the port
//data reads:
b = inb(EPP_DATA);
// address write:
outb(char xxx,EPP_ADDR);
//address read:
b=inb(EPP_ADDR);
/* 32 bit write (write four bytes successively - very fast, not supported
on all hardware) */
//outw(long int xxx,EPP_DATA);
//release permission
ioperm( PORT, 8, 0 );
-- To unsubscribe, send mail to: linux-parport-request@torque.net --
-- with the single word "unsubscribe" in the body of the message. --
This archive was generated by hypermail 2b29 : Mon Apr 03 2000 - 19:09:38 EDT