root/ldd3-samples-1.0.0/short/short.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. short_incr_bp
  2. short_open
  3. short_release
  4. do_short_read
  5. short_read
  6. do_short_write
  7. short_write
  8. short_poll
  9. short_i_read
  10. short_i_write
  11. short_interrupt
  12. short_incr_tv
  13. short_do_tasklet
  14. short_wq_interrupt
  15. short_tl_interrupt
  16. short_sh_interrupt
  17. short_kernelprobe
  18. short_probing
  19. short_selfprobe
  20. short_init
  21. short_cleanup

/*
 * short.c -- Simple Hardware Operations and Raw Tests
 * short.c -- also a brief example of interrupt handling ("short int")
 *
 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
 * Copyright (C) 2001 O'Reilly & Associates
 *
 * The source code in this file can be freely used, adapted,
 * and redistributed in source or binary form, so long as an
 * acknowledgment appears in derived source files.  The citation
 * should list that the code comes from the book "Linux Device
 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 * by O'Reilly & Associates.   No warranty is attached;
 * we cannot take responsibility for errors or fitness for use.
 *
 * $Id: short.c,v 1.16 2004/10/29 16:45:40 corbet Exp $
 */

/*
 * FIXME: this driver is not safe with concurrent readers or
 * writers.
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/sched.h>
#include <linux/kernel.h>       /* printk() */
#include <linux/fs.h>           /* everything... */
#include <linux/errno.h>        /* error codes */
#include <linux/delay.h>        /* udelay */
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/poll.h>
#include <linux/wait.h>

#include <asm/io.h>

#define SHORT_NR_PORTS  8       /* use 8 ports by default */

/*
 * all of the parameters have no "short_" prefix, to save typing when
 * specifying them at load time
 */
static int major = 0;   /* dynamic by default */
module_param(major, int, 0);

static int use_mem = 0; /* default is I/O-mapped */
module_param(use_mem, int, 0);

/* default is the first printer port on PC's. "short_base" is there too
   because it's what we want to use in the code */
static unsigned long base = 0x378;
unsigned long short_base = 0;
module_param(base, long, 0);

/* The interrupt line is undefined by default. "short_irq" is as above */
static int irq = -1;
volatile int short_irq = -1;
module_param(irq, int, 0);

static int probe = 0;   /* select at load time how to probe irq line */
module_param(probe, int, 0);

static int wq = 0;      /* select at load time whether a workqueue is used */
module_param(wq, int, 0);

static int tasklet = 0; /* select whether a tasklet is used */
module_param(tasklet, int, 0);

static int share = 0;   /* select at load time whether install a shared irq */
module_param(share, int, 0);

MODULE_AUTHOR ("Alessandro Rubini");
MODULE_LICENSE("Dual BSD/GPL");


unsigned long short_buffer = 0;
unsigned long volatile short_head;
volatile unsigned long short_tail;
DECLARE_WAIT_QUEUE_HEAD(short_queue);

/* Set up our tasklet if we're doing that. */
void short_do_tasklet(unsigned long);
DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);

/*
 * Atomicly increment an index into short_buffer
 */
static inline void short_incr_bp(volatile unsigned long *index, int delta)
{
        unsigned long new = *index + delta;
        barrier();  /* Don't optimize these two together */
        *index = (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new;
}


/*
 * The devices with low minor numbers write/read burst of data to/from
 * specific I/O ports (by default the parallel ones).
 * 
 * The device with 128 as minor number returns ascii strings telling
 * when interrupts have been received. Writing to the device toggles
 * 00/FF on the parallel data lines. If there is a loopback wire, this
 * generates interrupts.  
 */

int short_open (struct inode *inode, struct file *filp)
{
        extern struct file_operations short_i_fops;

        if (iminor (inode) & 0x80)
                filp->f_op = &short_i_fops; /* the interrupt-driven node */
        return 0;
}


int short_release (struct inode *inode, struct file *filp)
{
        return 0;
}


/* first, the port-oriented device */

enum short_modes {SHORT_DEFAULT=0, SHORT_PAUSE, SHORT_STRING, SHORT_MEMORY};

ssize_t do_short_read (struct inode *inode, struct file *filp, char __user *buf,
                size_t count, loff_t *f_pos)
{
        int retval = count, minor = iminor (inode);
        unsigned long port = short_base + (minor&0x0f);
        void *address = (void *) short_base + (minor&0x0f);
        int mode = (minor&0x70) >> 4;
        unsigned char *kbuf = kmalloc(count, GFP_KERNEL), *ptr;
    
        if (!kbuf)
                return -ENOMEM;
        ptr = kbuf;

        if (use_mem)
                mode = SHORT_MEMORY;
        
        switch(mode) {
            case SHORT_STRING:
                insb(port, ptr, count);
                rmb();
                break;

            case SHORT_DEFAULT:
                while (count--) {
                        *(ptr++) = inb(port);
                        rmb();
                }
                break;

            case SHORT_MEMORY:
                while (count--) {
                        *ptr++ = ioread8(address);
                        rmb();
                }
                break;
            case SHORT_PAUSE:
                while (count--) {
                        *(ptr++) = inb_p(port);
                        rmb();
                }
                break;

            default: /* no more modes defined by now */
                retval = -EINVAL;
                break;
        }
        if ((retval > 0) && copy_to_user(buf, kbuf, retval))
                retval = -EFAULT;
        kfree(kbuf);
        return retval;
}


/*
 * Version-specific methods for the fops structure.  FIXME don't need anymore.
 */
ssize_t short_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
        return do_short_read(filp->f_dentry->d_inode, filp, buf, count, f_pos);
}



ssize_t do_short_write (struct inode *inode, struct file *filp, const char __user *buf,
                size_t count, loff_t *f_pos)
{
        int retval = count, minor = iminor(inode);
        unsigned long port = short_base + (minor&0x0f);
        void *address = (void *) short_base + (minor&0x0f);
        int mode = (minor&0x70) >> 4;
        unsigned char *kbuf = kmalloc(count, GFP_KERNEL), *ptr;

        if (!kbuf)
                return -ENOMEM;
        if (copy_from_user(kbuf, buf, count))
                return -EFAULT;
        ptr = kbuf;

        if (use_mem)
                mode = SHORT_MEMORY;

        switch(mode) {
        case SHORT_PAUSE:
                while (count--) {
                        outb_p(*(ptr++), port);
                        wmb();
                }
                break;

        case SHORT_STRING:
                outsb(port, ptr, count);
                wmb();
                break;

        case SHORT_DEFAULT:
                while (count--) {
                        outb(*(ptr++), port);
                        wmb();
                }
                break;

        case SHORT_MEMORY:
                while (count--) {
                        iowrite8(*ptr++, address);
                        wmb();
                }
                break;

        default: /* no more modes defined by now */
                retval = -EINVAL;
                break;
        }
        kfree(kbuf);
        return retval;
}


ssize_t short_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
        return do_short_write(filp->f_dentry->d_inode, filp, buf, count, f_pos);
}




unsigned int short_poll(struct file *filp, poll_table *wait)
{
        return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
}






struct file_operations short_fops = {
        .owner   = THIS_MODULE,
        .read    = short_read,
        .write   = short_write,
        .poll    = short_poll,
        .open    = short_open,
        .release = short_release,
};

/* then,  the interrupt-related device */

ssize_t short_i_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
        int count0;
        DEFINE_WAIT(wait);

        while (short_head == short_tail) {
                prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);
                if (short_head == short_tail)
                        schedule();
                finish_wait(&short_queue, &wait);
                if (signal_pending (current))  /* a signal arrived */
                        return -ERESTARTSYS; /* tell the fs layer to handle it */
        } 
        /* count0 is the number of readable data bytes */
        count0 = short_head - short_tail;
        if (count0 < 0) /* wrapped */
                count0 = short_buffer + PAGE_SIZE - short_tail;
        if (count0 < count) count = count0;

        if (copy_to_user(buf, (char *)short_tail, count))
                return -EFAULT;
        short_incr_bp (&short_tail, count);
        return count;
}

ssize_t short_i_write (struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
        int written = 0, odd = *f_pos & 1;
        unsigned long port = short_base; /* output to the parallel data latch */
        void *address = (void *) short_base;

        if (use_mem) {
                while (written < count)
                        iowrite8(0xff * ((++written + odd) & 1), address);
        } else {
                while (written < count)
                        outb(0xff * ((++written + odd) & 1), port);
        }

        *f_pos += count;
        return written;
}




struct file_operations short_i_fops = {
        .owner   = THIS_MODULE,
        .read    = short_i_read,
        .write   = short_i_write,
        .open    = short_open,
        .release = short_release,
};

irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
        struct timeval tv;
        int written;

        do_gettimeofday(&tv);

            /* Write a 16 byte record. Assume PAGE_SIZE is a multiple of 16 */
        written = sprintf((char *)short_head,"%08u.%06u\n",
                        (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
        BUG_ON(written != 16);
        short_incr_bp(&short_head, written);
        wake_up_interruptible(&short_queue); /* awake any reading process */
        return IRQ_HANDLED;
}

/*
 * The following two functions are equivalent to the previous one,
 * but split in top and bottom half. First, a few needed variables
 */

#define NR_TIMEVAL 512 /* length of the array of time values */

struct timeval tv_data[NR_TIMEVAL]; /* too lazy to allocate it */
volatile struct timeval *tv_head=tv_data;
volatile struct timeval *tv_tail=tv_data;

static struct work_struct short_wq;


int short_wq_count = 0;

/*
 * Increment a circular buffer pointer in a way that nobody sees
 * an intermediate value.
 */
static inline void short_incr_tv(volatile struct timeval **tvp)
{
        if (*tvp == (tv_data + NR_TIMEVAL - 1))
                *tvp = tv_data;  /* Wrap */
        else
                (*tvp)++;
}



void short_do_tasklet (unsigned long unused)
{
        int savecount = short_wq_count, written;
        short_wq_count = 0; /* we have already been removed from the queue */
        /*
         * The bottom half reads the tv array, filled by the top half,
         * and prints it to the circular text buffer, which is then consumed
         * by reading processes
         */

        /* First write the number of interrupts that occurred before this bh */
        written = sprintf((char *)short_head,"bh after %6i\n",savecount);
        short_incr_bp(&short_head, written);

        /*
         * Then, write the time values. Write exactly 16 bytes at a time,
         * so it aligns with PAGE_SIZE
         */

        do {
                written = sprintf((char *)short_head,"%08u.%06u\n",
                                (int)(tv_tail->tv_sec % 100000000),
                                (int)(tv_tail->tv_usec));
                short_incr_bp(&short_head, written);
                short_incr_tv(&tv_tail);
        } while (tv_tail != tv_head);

        wake_up_interruptible(&short_queue); /* awake any reading process */
}


irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
        /* Grab the current time information. */
        do_gettimeofday((struct timeval *) tv_head);
        short_incr_tv(&tv_head);

        /* Queue the bh. Don't worry about multiple enqueueing */
        schedule_work(&short_wq);

        short_wq_count++; /* record that an interrupt arrived */
        return IRQ_HANDLED;
}


/*
 * Tasklet top half
 */

irqreturn_t short_tl_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
        do_gettimeofday((struct timeval *) tv_head); /* cast to stop 'volatile' warning */
        short_incr_tv(&tv_head);
        tasklet_schedule(&short_tasklet);
        short_wq_count++; /* record that an interrupt arrived */
        return IRQ_HANDLED;
}




irqreturn_t short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
        int value, written;
        struct timeval tv;

        /* If it wasn't short, return immediately */
        value = inb(short_base);
        if (!(value & 0x80))
                return IRQ_NONE;
        
        /* clear the interrupting bit */
        outb(value & 0x7F, short_base);

        /* the rest is unchanged */

        do_gettimeofday(&tv);
        written = sprintf((char *)short_head,"%08u.%06u\n",
                        (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
        short_incr_bp(&short_head, written);
        wake_up_interruptible(&short_queue); /* awake any reading process */
        return IRQ_HANDLED;
}

void short_kernelprobe(void)
{
        int count = 0;
        do {
                unsigned long mask;

                mask = probe_irq_on();
                outb_p(0x10,short_base+2); /* enable reporting */
                outb_p(0x00,short_base);   /* clear the bit */
                outb_p(0xFF,short_base);   /* set the bit: interrupt! */
                outb_p(0x00,short_base+2); /* disable reporting */
                udelay(5);  /* give it some time */
                short_irq = probe_irq_off(mask);

                if (short_irq == 0) { /* none of them? */
                        printk(KERN_INFO "short: no irq reported by probe\n");
                        short_irq = -1;
                }
                /*
                 * if more than one line has been activated, the result is
                 * negative. We should service the interrupt (no need for lpt port)
                 * and loop over again. Loop at most five times, then give up
                 */
        } while (short_irq < 0 && count++ < 5);
        if (short_irq < 0)
                printk("short: probe failed %i times, giving up\n", count);
}

irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs)
{
        if (short_irq == 0) short_irq = irq;    /* found */
        if (short_irq != irq) short_irq = -irq; /* ambiguous */
        return IRQ_HANDLED;
}

void short_selfprobe(void)
{
        int trials[] = {3, 5, 7, 9, 0};
        int tried[]  = {0, 0, 0, 0, 0};
        int i, count = 0;

        /*
         * install the probing handler for all possible lines. Remember
         * the result (0 for success, or -EBUSY) in order to only free
         * what has been acquired
      */
        for (i = 0; trials[i]; i++)
                tried[i] = request_irq(trials[i], short_probing,
                                SA_INTERRUPT, "short probe", NULL);

        do {
                short_irq = 0; /* none got, yet */
                outb_p(0x10,short_base+2); /* enable */
                outb_p(0x00,short_base);
                outb_p(0xFF,short_base); /* toggle the bit */
                outb_p(0x00,short_base+2); /* disable */
                udelay(5);  /* give it some time */

                /* the value has been set by the handler */
                if (short_irq == 0) { /* none of them? */
                        printk(KERN_INFO "short: no irq reported by probe\n");
                }
                /*
                 * If more than one line has been activated, the result is
                 * negative. We should service the interrupt (but the lpt port
                 * doesn't need it) and loop over again. Do it at most 5 times
                 */
        } while (short_irq <=0 && count++ < 5);

        /* end of loop, uninstall the handler */
        for (i = 0; trials[i]; i++)
                if (tried[i] == 0)
                        free_irq(trials[i], NULL);

        if (short_irq < 0)
                printk("short: probe failed %i times, giving up\n", count);
}



/* Finally, init and cleanup */

int short_init(void)
{
        int result;

        /*
         * first, sort out the base/short_base ambiguity: we'd better
         * use short_base in the code, for clarity, but allow setting
         * just "base" at load time. Same for "irq".
         */
        short_base = base;
        short_irq = irq;

        /* Get our needed resources. */
        if (!use_mem) {
                if (! request_region(short_base, SHORT_NR_PORTS, "short")) {
                        printk(KERN_INFO "short: can't get I/O port address 0x%lx\n",
                                        short_base);
                        return -ENODEV;
                }

        } else {
                if (! request_mem_region(short_base, SHORT_NR_PORTS, "short")) {
                        printk(KERN_INFO "short: can't get I/O mem address 0x%lx\n",
                                        short_base);
                        return -ENODEV;
                }

                /* also, ioremap it */
                short_base = (unsigned long) ioremap(short_base, SHORT_NR_PORTS);
                /* Hmm... we should check the return value */
        }
        /* Here we register our device - should not fail thereafter */
        result = register_chrdev(major, "short", &short_fops);
        if (result < 0) {
                printk(KERN_INFO "short: can't get major number\n");
                release_region(short_base,SHORT_NR_PORTS);  /* FIXME - use-mem case? */
                return result;
        }
        if (major == 0) major = result; /* dynamic */

        short_buffer = __get_free_pages(GFP_KERNEL,0); /* never fails */  /* FIXME */
        short_head = short_tail = short_buffer;

        /*
         * Fill the workqueue structure, used for the bottom half handler.
         * The cast is there to prevent warnings about the type of the
         * (unused) argument.
         */
        /* this line is in short_init() */
        INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);

        /*
         * Now we deal with the interrupt: either kernel-based
         * autodetection, DIY detection or default number
         */

        if (short_irq < 0 && probe == 1)
                short_kernelprobe();

        if (short_irq < 0 && probe == 2)
                short_selfprobe();

        if (short_irq < 0) /* not yet specified: force the default on */
                switch(short_base) {
                    case 0x378: short_irq = 7; break;
                    case 0x278: short_irq = 2; break;
                    case 0x3bc: short_irq = 5; break;
                }

        /*
         * If shared has been specified, installed the shared handler
         * instead of the normal one. Do it first, before a -EBUSY will
         * force short_irq to -1.
         */
        if (short_irq >= 0 && share > 0) {
                result = request_irq(short_irq, short_sh_interrupt,
                                SA_SHIRQ | SA_INTERRUPT,"short",
                                short_sh_interrupt);
                if (result) {
                        printk(KERN_INFO "short: can't get assigned irq %i\n", short_irq);
                        short_irq = -1;
                }
                else { /* actually enable it -- assume this *is* a parallel port */
                        outb(0x10, short_base+2);
                }
                return 0; /* the rest of the function only installs handlers */
        }

        if (short_irq >= 0) {
                result = request_irq(short_irq, short_interrupt,
                                SA_INTERRUPT, "short", NULL);
                if (result) {
                        printk(KERN_INFO "short: can't get assigned irq %i\n",
                                        short_irq);
                        short_irq = -1;
                }
                else { /* actually enable it -- assume this *is* a parallel port */
                        outb(0x10,short_base+2);
                }
        }

        /*
         * Ok, now change the interrupt handler if using top/bottom halves
         * has been requested
         */
        if (short_irq >= 0 && (wq + tasklet) > 0) {
                free_irq(short_irq,NULL);
                result = request_irq(short_irq,
                                tasklet ? short_tl_interrupt :
                                short_wq_interrupt,
                                SA_INTERRUPT,"short-bh", NULL);
                if (result) {
                        printk(KERN_INFO "short-bh: can't get assigned irq %i\n",
                                        short_irq);
                        short_irq = -1;
                }
        }

        return 0;
}

void short_cleanup(void)
{
        if (short_irq >= 0) {
                outb(0x0, short_base + 2);   /* disable the interrupt */
                if (!share) free_irq(short_irq, NULL);
                else free_irq(short_irq, short_sh_interrupt);
        }
        /* Make sure we don't leave work queue/tasklet functions running */
        if (tasklet)
                tasklet_disable(&short_tasklet);
        else
                flush_scheduled_work();
        unregister_chrdev(major, "short");
        if (use_mem) {
                iounmap((void __iomem *)short_base);
                release_mem_region(short_base, SHORT_NR_PORTS);
        } else {
                release_region(short_base,SHORT_NR_PORTS);
        }
        if (short_buffer) free_page(short_buffer);
}

module_init(short_init);
module_exit(short_cleanup);

/* [<][>][^][v][top][bottom][index][help] */