转自:http://blog.sina.com.cn/s/blog_5f84dc840100o4u4.html
/ *
After you compile it, when you run the shell scriptshort_load, maybe you'll encounter the following error:
insmod: error inserting './short.ko': -1 No such device.
If you have a look inside the short_load script, you will findthis file 'short.ko' does exist. There is no error in it.
If you run dmesg(or more /var/log/messages), you will find thecause.
'short: can't get I/O port address0x378'
Some other module has already occupied thisport.
Run the command 'more /proc/ioports', you will find that0378-037a is occupied by parport.
You must unload them using following commands in the sameorder:
#rmmod lp
#rmmod parport_pc
#rmmod parport
Then you can run short_load correctly.
I inserted one LED to pin 2 and groundpin. But no matter whatever I input, the LED light always keptlighting. I don't know where iswrong.
* /
/ *
* short.c -- Simple Hardware Operations and RawTests
* short.c -- also a brief example of interrupthandling ("short int")
*
* Copyright (C) 2001 Alessandro Rubini andJonathan Corbet
* Copyright (C) 2001 O'Reilly &Associates
*
* The source code in this file can be freelyused, adapted,
* and redistributed in source or binary form, solong as an
* acknowledgment appears in derived sourcefiles. The citation
* should list that the code comes from the book"Linux Device
* Drivers" by Alessandro Rubini and JonathanCorbet, published
* by O'Reilly &Associates. No warranty isattached;
* we cannot take responsibility for errors orfitness for use.
*
* $Id: short.c,v 1.16 2004/10/29 16:45:40 corbetExp $
* /
/ *
* FIXME: this driver is not safe with concurrentreaders 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> / * errorcodes * /
#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" isthere too
because it's what we want touse 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" isas 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 timewhether 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 intoshort_buffer
* /
static inline void short_incr_bp(volatile unsigned long *index, intdelta)
{
unsignedlong 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/readburst of data to/from
* specific I/O ports (by default the parallelones).
*
* The device with 128 as minor number returnsascii strings telling
* when interrupts have been received. Writing tothe device toggles
* 00/FF on the parallel data lines. If there is aloopback wire, this
* generates interrupts.
* /
int short_open (struct inode *inode, struct file *filp)
{
externstruct file_operations short_i_fops;
/ * ifminor device number is greater than or equal to 128, then useinterrupt-driven node* /
/ * So tospeak, /dev/shortint or /dev/shortprint * /
if(iminor (inode) & 0x80)
{
filp->f_op = &short_i_fops;
} / * the interrupt-driven node * /
return0;
}
int short_release (struct inode *inode, struct file *filp)
{
return0;
}
/ * 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);
unsignedlong port = short_base + (minor & 0x0f);
void*address = (void *) short_base + (minor &0x0f);
int mode =(minor & 0x70) >>4;
unsignedchar *kbuf = kmalloc(count, GFP_KERNEL), *ptr;
if(!kbuf)
{
return -ENOMEM;
}
ptr =kbuf;
if(use_mem)
{
mode = SHORT_MEMORY;
}
switch(mode)
{
caseSHORT_STRING:
insb(port, ptr, count);
rmb();
break;
caseSHORT_DEFAULT:
while (count--)
{
*(ptr++) = inb(port);
rmb();
}
break;
caseSHORT_MEMORY:
while (count--)
{
*ptr++ = ioread8(address);
rmb();
}
break;
caseSHORT_PAUSE:
while (count--)
{
*(ptr++) = inb_p(port);
rmb();
}
break;
default: / * no more modes defined by now * /
retval = -EINVAL;
break;
}
if((retval > 0) &©_to_user(buf, kbuf, retval))
{
retval = -EFAULT;
}
kfree(kbuf);
returnretval;
}
/ *
* Version-specific methods for the fopsstructure. FIXME don't need anymore.
* /
ssize_t short_read(struct file *filp, char __user *buf, size_tcount, loff_t *f_pos)
{
returndo_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);
unsignedlong port = short_base + (minor & 0x0f);
void*address = (void *) short_base + (minor &0x0f);
int mode =(minor & 0x70) >>4;
unsignedchar *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)
{
caseSHORT_PAUSE:
while (count--)
{
outb_p(*(ptr++), port);
wmb();
}
break;
caseSHORT_STRING:
outsb(port, ptr, count);
wmb();
break;
caseSHORT_DEFAULT: / *对应于设备/dev/short0* /
while (count--)
{
outb(*(ptr++), port); / *一次向port写入一个字节8位* /
wmb();
}
break;
caseSHORT_MEMORY:
while (count--)
{
iowrite8(*ptr++, address);
wmb();
}
break;
default:/ * no more modes defined by now * /
retval = -EINVAL;
break;
}
kfree(kbuf);
returnretval;
}
ssize_t short_write(struct file *filp, const char __user *buf,size_t count,
loff_t *f_pos)
{
returndo_short_write(filp->f_dentry->d_inode,filp, buf, count, f_pos);
}
unsigned int short_poll(struct file *filp, poll_table*wait)
{
returnPOLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
}
struct file_operations short_fops =
{
.owner = THIS_MODULE,
.read =short_read,
.write = short_write,
.poll =short_poll, / * 对应select操作* /
.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)
{
intcount0;
DEFINE_WAIT(wait);
while(short_head == short_tail) / * circular buffer is empty.* /
{
prepare_to_wait(&short_queue,&wait, TASK_INTERRUPTIBLE);
if (short_head == short_tail)
{
schedule();
}
finish_wait(&short_queue,&wait);
/ *signal_pending( current)―――》检查当前进程是否有信号处理,返回不为0表示有信号需要处理。-ERESTARTSYS表示信号函数处理完毕后重新执行信号函数前的某个系统调用。也就是说,如果信号函数前有发生系统调用,在调度用户信号函数之前,内核会检查系统调用的返回值,看看是不是因为这个信号而中断了系统调用.如果返回值-ERESTARTSYS,并且当前调度的信号具备-ERESTARTSYS属性,系统就会在用户信号函数返回之后再执行该系统调用。*/
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); / *移动写指针*/
returncount;
}
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;
unsignedlong 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;
returnwritten;
}
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, structpt_regs *regs)
{
structtimeval tv;
intwritten;
do_gettimeofday(&tv);
/ * Writea 16 byte record. Assume PAGE_SIZE is a multiple of 16 * /
written =sprintf((char *)short_head, "u.u\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 anyreading process * /
returnIRQ_HANDLED;
}
/ *
* The following two functions are equivalent tothe previous one,
* but split in top and bottom half. First, a fewneeded variables
* /
#define NR_TIMEVAL 512 / * length of the array of time values */
struct timeval tv_data[NR_TIMEVAL]; / * too lazy to allocate it* / / *用于tasklet的另一个circular buffer,用于临时存储中间数据.* /
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 waythat 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)
{
intsavecount = 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 thenconsumed
* by reading processes
* /
/ * Firstwrite 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 atime,
* so it aligns with PAGE_SIZE
* /
do
{
written = sprintf((char *)short_head, "u.u\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 anyreading process * /
}
irqreturn_t short_wq_interrupt(int irq, void *dev_id, structpt_regs *regs)
{
/ * Grab thecurrent time information. * /
do_gettimeofday((struct timeval *) tv_head);
short_incr_tv(&tv_head);
/ * Queuethe bh. Don't worry about multiple enqueueing * /
schedule_work(&short_wq);
short_wq_count++; / * record that an interrupt arrived * /
returnIRQ_HANDLED;
}
/ *
* Tasklet top half
* /
irqreturn_t short_tl_interrupt(int irq, void *dev_id, structpt_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 * /
returnIRQ_HANDLED;
}
irqreturn_t short_sh_interrupt(int irq, void *dev_id, structpt_regs *regs)
{
int value,written;
structtimeval tv;
/ * If itwasn't short, return immediately * /
value =inb(short_base);
if (!(value& 0x80))
{
return IRQ_NONE;
}
/ * clearthe interrupting bit * /
outb(value& 0x7F, short_base);
/ * therest is unchanged * /
do_gettimeofday(&tv);
written =sprintf((char *)short_head, "u.u\n",
(int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
short_incr_bp(&short_head, written); / *修改circularbuffer 中的写指针* /
wake_up_interruptible(&short_queue); / * awake anyreading process * /
returnIRQ_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 lptport)
* 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 * /
returnIRQ_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));
/ * endof 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);
}
}
/ *
void* __ioremap(unsigned long phys_addr, unsigned long size, unsignedlong flags)
void*ioremap(unsigned long phys_addr, unsigned longsize)
入口: phys_addr:要映射的起始的IO地址;
size:要映射的空间的大小;
flags:要映射的IO空间的和权限有关的标志;
phys_addr:是要映射的物理地址,
size:是要映射的长度,
S3C2410的long是32位而非你说的64位。
功能:将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问;
实现:对要映射的IO地址空间进行判断,低PCI/ISA地址不需要重新映射,也不允许用户将IO地址空间映射到正在使用的RAM中,最后申请一个vm_area_struct结构,调用remap_area_pages填写页表,若填写过程不成功则释放申请的vm_area_struct空间;
ioremap 依靠__ioremap实现,它只是在__ioremap中以第三个参数为0调用来实现.
ioremap是内核提供的用来映射外设寄存器到主存的函数,我们要映射的地址已经从pci_dev中读了出来(上一步),这样就水到渠成的成功映射了而不会和其他地址有冲突。映射完了有什么效果呢,我举个例子,比如某个网卡有100个寄存器,他们都是连在一块的,位置是固定的,假如每个寄存器占4个字节,那么一共400个字节的空间被映射到内存成功后,ioaddr就是这段地址的开头(注意ioaddr是虚拟地址,而mmio_start是物理地址,它是BIOS得到的,肯定是物理地址,而保护模式下CPU不认物理地址,只认虚拟地址),ioaddr+0就是第一个寄存器的地址,ioaddr+4就是第二个寄存器地址(每个寄存器占4个字节),以此类推,我们就能够在内存中访问到所有的寄存器进而操控他们了。
void request_region(unsigned long from, unsigned long num, const char *name)
这个函数用来申请一块输入输出区域。如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports文件中可以看到你登记的io口。
参数1:io端口的基地址。
参数2:io端口占用的范围。
参数3:使用这段io地址的设备名。
在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。
request_region()用于内核为驱动“分配”端口,这里分配的意思是,记录该端口已经被某个进程使用,要是其它进程试图访问它,就会产生“忙”错误。所以目的在于实现资源的互斥访问。
反之,如果一个资源只被一个进程访问,不会导致资源的争用,这时request_region()是可选的。
释放使用 voidrelease_region(unsigned long from, unsigned long num)
几乎每一种外设都是通过读写设备上的寄存器来进行的。外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。而具体采用哪一种则取决于CPU的体系结构。
io-map的寄存器用outb/w/l,inb/w/l指令访问
mem-map的寄存器直接用指针赋值即可,但是由于对内存访问可能被gcc优化掉,所以最好使用linux提供的包装函数:ioread8,ioread16,ioread32,iowrite8,iowrite16,iowrite32(早期版本为readb/w/l,writeb/w/l)。
io-map也有相应的包装函数/宏。
* /
/ * Finally, init and cleanup * /
int short_init(void)
{
intresult;
/ *
* 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;
/ * Getour needed resources. * /
if(!use_mem) / * 对于IO mapped的IOport的操作,使用 request_region*/
{
if (!request_region(short_base,SHORT_NR_PORTS, "short")) / * IO mapped* /
{
printk(KERN_INFO "short: can't get I/O port address 0x%lx\n",
short_base);
return -ENODEV;
}
}
else/ *对于mem mapped的IOpoart操作,使用request_mem_region及 ioremap */
{
if (!request_mem_region(short_base, SHORT_NR_PORTS, "short"))/ * Mem Mapped* /
{
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 * /
}
/ * Herewe register our device - should not fail thereafter * /
/ *Inprevious code, register_chrdev_region is used. These two functionsare similar.* /
/ *
1.register_chrdev注册字符设备后,有0-255个子设备可用,若major==0,则内核动态申请主设备号。
staticinline int register_chrdev(unsigned int major, const char *name,const
struct file_operations *fops)
2.register_chrdev_region注册字符设备后,有count个子设备号可用(从from开始),dev_t from =MKDEV(major,minor)
int register_chrdev_region(dev_t from,unsigned count, const char *name)
* /
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; / *使用circular buffer*/
/ *
* Fill the workqueue structure, used for the bottom halfhandler.
* The cast is there to prevent warnings about the type of the
* (unused) argument.
* /
/ * thisline is in short_init() * /
/*工作队列的初始化。从2.6.20的内核开始,INIT_WORK宏做了改变,原来是三个参数,后来改成了两个参数* /
//INIT_WORK(&short_wq, (void(*)(void *))short_do_tasklet, NULL);
INIT_WORK(&short_wq, (void(*)(void *))short_do_tasklet);
/ *
* Now we deal with the interrupt: either kernel-based
* autodetection, DIY detection or default number
* /
/*使用内核提供的探测函数:probe_irq_on和probe_irq_off* /
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 -EBUSYwill
* force short_irq to -1.
* /
/*安装共享中断shared interrupt* /
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 */
}
/ *安装独享中断non-shared interrupt* /
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/bottomhalves
* has been requested
* /
/*安装workqueue和tasklet interrupt handler* /
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;
}
}
return0;
}
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);
}
}
/ * Makesure 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);