http://www.cs.usfca.edu/~cruse/cs635/tasklet.c
//-------------------------------------------------------------------
// tasklet.c
//
// This device-driver module illustrates a use of the kernel's
// 'tasklet' mechanism for deferring some of the work involved
// in responding to the serial UART's 'data receive' interrupt
// when FIFO-mode is enabled (i.e., this isr's 'bottom half').
//
// NOTE: Written and tested for Linux kernel version 2.6.22.5.
//
// programmer: ALLAN CRUSE
// written on: 22 OCT 2007
//-------------------------------------------------------------------
#include <linux/module.h> // for init_module()
#include <linux/proc_fs.h> // for create_proc_info_entry()
#include <linux/interrupt.h> // for schedule_tasklet()
#include <asm/uaccess.h> // for copy_to/from_user()
#include <asm/io.h> // for inb(), outb()
#define UARTBASE 0x03F8
#define DIVISOR_LATCH (UARTBASE+0)
#define TRANSMIT_DATA (UARTBASE+0)
#define RECEIVE_DATA (UARTBASE+0)
#define INTR_ENABLE (UARTBASE+1)
#define INTR_IDENTIFY (UARTBASE+2)
#define FIFO_CONTROL (UARTBASE+2)
#define LINE_CONTROL (UARTBASE+3)
#define MODEM_CONTROL (UARTBASE+4)
#define LINE_STATUS (UARTBASE+5)
#define MODEM_STATUS (UARTBASE+6)
#define UART_SCRATCH (UARTBASE+7)
#define UART_IRQ 0x04
#define RINGSIZE 64
char modname[] = "tasklet";
char devname[] = "uart";
int my_major = 84;
struct semaphore sem_busy;
wait_queue_head_t wq_read;
struct tasklet_struct rx_tasklet;
struct ringbuf {
int rxhead, rxtail;
int rxbuf[ RINGSIZE ];
} dev_info;
irqreturn_t my_isr( int irq, void *dev_id )
{
// read the UART interrupt-identification register
int intr_identify = inb( INTR_IDENTIFY )&0x0F;
// exit immediately if no interrupt is pending
if ( intr_identify == 0x01 ) return IRQ_NONE;
// otherwise the UART's receiver-FIFO holds data
// since we did not enable other UART interrupts
// step 1: immediately tell the sender to pause
outb( 0x09, MODEM_CONTROL ); // RTS=0
// step 2: then schedule 'bottom half' handling
tasklet_schedule( &rx_tasklet );
// step 3: finally, resume the interrupted task
return IRQ_HANDLED;
}
void my_rx_handler( unsigned long data )
{
struct ringbuf *mydev = (struct ringbuf *)data;
int i;
// move all the receiver-FIFO's data into our rx-ringbuffer
for (i = 0; i < 16; i++)
{
// input byte from FIFO and store in ringbuffer
mydev->rxbuf[ mydev->rxtail ] = inb( RECEIVE_DATA );
// advance the ringbuffer array's 'tail' index
mydev->rxtail = (1 + mydev->rxtail) % RINGSIZE;
// stop when the receiver-FIFO has been emptied
if ( ( inb( LINE_STATUS )&0x01 ) == 0x00 ) break;
// otherwise make sure the ringbuffer is yet full
}
// then awaken any sleeping reader-tasks
wake_up_interruptible( &wq_read );
}
int my_open( struct inode *inode, struct file *file )
{
// we will allow only one user at a time
down_interruptible( &sem_busy );
// reset our rx-ringbuffer to 'empty'
dev_info.rxhead = 0;
dev_info.rxtail = 0;
// raise the 'Data Terminal Ready' signal-line
outb( 0x09, MODEM_CONTROL ); // set DTR=1, RTS=0
return 0; // SUCCESS
}
ssize_t my_read( struct file *file, char *buf, size_t len, loff_t *pos )
{
int i, datum;
// if no data is in our rx-ringbuffer, issue 'Request-To-Send'
if ( dev_info.rxhead == dev_info.rxtail )
outb( 0x0B, MODEM_CONTROL ); // issue RTS=1 and DTR=1
// then sleep until some data is present in the rx-ringbuffer
if ( wait_event_interruptible( wq_read,
dev_info.rxhead != dev_info.rxtail ) ) return -ERESTARTSYS;
// stop the sender from transmitting more data
outb( 0x09, MODEM_CONTROL ); // set RTS=0
// extract data from our rx-ringbuffer and deliver it to the user
for (i = 0; i < len; i++)
{
// stop whenever our rx-ringbuffer becomes empty
if ( dev_info.rxhead == dev_info.rxtail ) break;
// copy the byte at the front of our rx-ringbuffer
datum = dev_info.rxbuf[ dev_info.rxhead ];
// put that copied byte into the user's buffer
if ( put_user( datum, buf+i ) ) return -EFAULT;
// advance our ringbuffer array's 'head' index
dev_info.rxhead = (1 + dev_info.rxhead) % RINGSIZE;
}
// in case our rx-ringbuffer is empty, issue 'Request-To-Send'
if ( dev_info.rxhead == dev_info.rxtail )
outb( 0x0B, MODEM_CONTROL ); // set RTS=1
// tell kernel how many bytes were put into user's buffer
return i;
}
ssize_t my_write( struct file *file, const char *buf, size_t len, loff_t *pos )
{
int datum;
// issue 'Request-To-Send'
outb( 0x0A, MODEM_CONTROL ); // RTS=1, DTR=0
// do busy-waiting until 'Clear-To-Send' becomes 'true'
while ( ( inb( MODEM_STATUS )&0x10 ) == 0x00 )
{
schedule();
if ( signal_pending( current ) ) return -EINTR;
}
// turn off the 'Request-To-Send' signal
outb( 0x09, MODEM_CONTROL ); // RTS=0, DTR=1
// do busy-waiting if necessary until THRE==1
while ( ( inb( LINE_STATUS ) & 0x20 ) == 0x00 );
// output a data-byte from the user's buffer to the UART
if ( get_user( datum, buf ) ) return -EFAULT;
outb( datum, TRANSMIT_DATA );
// tell the kernel that we transferred one byte
return 1;
}
int my_close( struct inode *inode, struct file *file )
{
// lower the 'Request-To-Send' signal-line
outb( 0x09, MODEM_CONTROL ); // set DTR=1, RTS=0
// release the semaphore
up( &sem_busy );
return 0; // SUCCESS
}
struct file_operations my_fops = {
owner: THIS_MODULE,
open: my_open,
read: my_read,
write: my_write,
release: my_close,
};
int my_get_info( char *buf, char **start, off_t off, int count )
{
int i, j, ch, len = 0;
// display the current contents of our rx-ringbuffer
for (i = 0; i < RINGSIZE; i+=16)
{
len += sprintf( buf+len, "\n %04X: ", i );
for (j = 0; j < 16; j++)
{
ch = dev_info.rxbuf[ i+j ];
len += sprintf( buf+len, "%02X ", ch );
}
for (j = 0; j < 16; j++)
{
ch = dev_info.rxbuf[ i+j ];
if (( ch < 0x20 )||( ch > 0x7E )) ch = '.';
len += sprintf( buf+len, "%c", ch );
}
}
len += sprintf( buf+len, "\n\n" );
// display the array's current 'head' and 'tail' indices
len += sprintf( buf+len, " rxhead=0x%04X ", dev_info.rxhead );
len += sprintf( buf+len, " rxtail=0x%04X ", dev_info.rxtail );
len += sprintf( buf+len, "\n\n" );
return len;
}
static void __exit my_exit(void )
{
// disable further UART interrupts and empty the FIFOs
outb( 0x00, MODEM_CONTROL );
outb( 0x00, INTR_ENABLE );
outb( 0x00, FIFO_CONTROL );
// flush any stale data
while ( inb( LINE_STATUS )&0x01 ) inb( RECEIVE_DATA );
inb( MODEM_STATUS );
inb( INTR_IDENTIFY );
// remove our UART interrupt-handler (including 'tasklet')
free_irq( UART_IRQ, &dev_info );
tasklet_kill( &rx_tasklet );
// unregister this driver and delete our '/proc' pseudo-file
unregister_chrdev( my_major, devname );
remove_proc_entry( modname, NULL );
printk( "<1>Removing \'%s\' module\n", modname );
}
static int __init my_init( void )
{
printk( "<1>\nInstalling \'%s\' module ", modname );
printk( "(major=%d) \n", my_major );
// initialize this driver's data-structures
init_MUTEX( &sem_busy );
init_waitqueue_head( &wq_read );
tasklet_init( &rx_tasklet, my_rx_handler, (unsigned long)&dev_info );
// install our handler for the UART's interrupts
if ( request_irq( UART_IRQ, &my_isr, IRQF_DISABLED,
modname, &dev_info ) ) return -EBUSY;
// configure the UART's operating mode
outb( 0x00, INTR_ENABLE );
outb( 0x00, FIFO_CONTROL );
outb( 0x80, LINE_CONTROL );
outw( 0x0001, DIVISOR_LATCH );
outb( 0x03, LINE_CONTROL );
outb( 0x09, MODEM_CONTROL );
outb( 0x01, INTR_ENABLE );
outb( 0xC3, FIFO_CONTROL );
// eliminate any stale register-values
inb( INTR_IDENTIFY );
inb( MODEM_STATUS );
inb( LINE_STATUS );
inb( RECEIVE_DATA );
// for debugging: confirm the contents of UART registers
printk( " Modem Control: %02X \n", inb( MODEM_CONTROL ) );
printk( " Modem Status: %02X \n", inb( MODEM_STATUS ) );
printk( " Line Control: %02X \n", inb( LINE_CONTROL ) );
printk( " Line Status: %02X \n", inb( LINE_STATUS ) );
printk( " Intr Enable: %02X \n", inb( INTR_ENABLE ) );
printk( " Intr Identify: %02X \n", inb( INTR_IDENTIFY ) );
// create our '/proc' pseudo-file and register this driver
create_proc_info_entry( modname, 0, NULL, my_get_info );
return register_chrdev( my_major, devname, &my_fops );
}
module_init( my_init );
module_exit( my_exit );
MODULE_LICENSE("GPL");