[PARPORT] Parallel port user-space driver and interrupts

From: Carl Michal (michal@physics.ubc.ca)
Date: Mon Apr 03 2000 - 19:04:57 EDT

  • Next message: Tim Waugh: "Re: [PARPORT] Parallel port user-space driver and interrupts"

    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( &current->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