LINUX设备驱动程序笔记(五)中断处理
<一> 中断处理流程例如以下:
1、发生中断时,CPU运行异常向量vector_irq的代码。
2、在vector_irq里面。终于会调用中断处理的总入口函数asm_do_IRQ。
3、asm_do_IRQ依据中断号调用irq_desc数组项中的handle_irq。
4、hadnle_irq会使用chip成员中的函数来设置硬件,比方清除中断、禁止中断、又一次使能中断等。
5、handle_irq逐个调用用户在action链表中注冊的处理函数。
<二>安装中断处理例程
注冊/释放中断的函数:
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs*), unsigned long flags,
const char *dev_name, void *dev_id);
void free_irq(unsigned int irq, void *dev_id);
unsigned int irq:申请的中断号
irqreturn_t (*handler)(int, void *, struct pt_regs*):安装的中断处理函数指针
unsigned long flags:中断管理有关的位掩码选项,中断的触发方式。如SA_INTERRUPT、SA_SHIRQ等
const char *dev_name:传递给request_irq的字符串。中断名字
void *dev_id:该指针仅仅用于共享的中断信号线。没有使用共享时,dev_id设置为NULL。
中断处理例程能够在驱动程序初始化时或设备第一次打开时安装。但考虑中断资源有限,调用request_irq的正确位置应该是在设备第一次打开、硬件被告知产生中断之前。调用free_irq的位置是最后一次关闭设备、硬件被告知不用再中断处理器之后。
<三>实现中断处理例程
中断处理例程事实上也是一个C函数程序,但因为发生在中断时间内,所以它有下面特点:
1、处理例程不能向用户空间发送或者接收数据,由于它不是在不论什么进程的上下文中运行的。
2、处理例程不能做不论什么可能发生休眠的操作,比如调用wait_event,锁住一个信号量等。
3、处理例程不能调用schdule函数。
中断处理例程的功能就是将有关中断接收的信息反馈给设备,并依据正在服务的中断的不同含义对数据进行对应的读或写。它的一个典型任务就是:假设中断通知进程所等待的事件已经发生,比方新的数据到达,就会唤醒在该设备上休眠的进程。
不管是高速还是慢速处理例程,程序猿都应该编写运行时间尽可能短的处理例程。假设须要运行一个长时间的计算任务。最好的方法是使用tasklet或者工作队列在更安全的时间内调度计算任务。
中断处理例程irqreturn_t (*handler)(int, void *, struct pt_regs*)的參数及返回值:
int irq:中断号
void *dev_id:一种客户数据类型即驱动程序可用的私有数据,传递给request_irq函数的void *參数会在中断发生时作为參数被传回处理例程。通常,我们会为dev_id传递一个指向自己设备的数据结构指针。比如:
static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs) { struct sample_dev *dev = dev_id; …… } /*与该处理例程相关联的典型open代码例如以下所看到的:*/ static void sample_open(struct inode *inode, struct file *filp) { struct sample_dev *dev = hwinfo + MINOR(inode->i_rdev); request_irq(dev->irq, sample_interrupt, 0, "sample", (void *)dev); …… return 0; }
struct pt_reg *regs:非常少使用。
返回值有三种:IRQ_HANDLED。IRQ_NONE;IRQ_RETVAL(handled);
禁用和启用中断:
有时设备驱动程序必须在一个时间内堵塞中断的发生。
中断的禁用分为禁用单个中断和禁用全部中断。
禁用单个中断
#include <asm/irq.h>
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);
禁用全部中断
#include <asm/irq.h>
void local_irq_save(unsigned long flags);
void local_irq_disable(void);
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
<四>顶半部和底半部
中断处理的一个主要问题是如何在处理例程内完毕耗时的任务。对应一次设备中断须要完毕一定数量的工作。可是中断处理例程须要尽快结束而不能使终端堵塞的时间过长,这两个需求彼此冲突。
Linux通过将中断处理例程分成两部分来解决问题。
称为顶半部的部分。是实际响应中断的例程,也就是用request_irq注冊的中断例程;而所谓的底半部是一个被顶半部调度。并在稍后更安全的时间内运行的例程。顶半部处理例程和底半部处理例程之间最大的不同,就是当底半部处理例程运行时,全部的中断都是打开的——这就是所谓的更安全时间内运行。
典型的情况是顶半部保存设备的数据到一个设备特定的缓冲区并调度它的底半部。然后退出,这个操作是很快的。然后。底半部运行其它必要的工作。比如唤醒进程、启动另外的I/O操作等等。这样的方式同意在底半部工作期间,顶半部还能够继续为新的中断服务。
Linux内核有两种不同机制能够用来实现底半部处理:tasklet和工作队列。tasklet一般是底半部处理的优选机制,由于它很快,可是全部的tasklet代码必须是原子的。工作队列具有更高的延迟,但同意休眠。
tasklet:tasklet是一个能够由系统决定的安全时刻在软件中断上下文被调度执行的特殊函数。
使用宏DECLARE_TASKLET声明tasklet:DECLARE_TASKLET(name, function, data);当中的參数name是给tasklet起的名字,function是运行tasklet时调用的函数(它带有一个unsigned long型的參数而且返回void)。data是一个用来传递给tasklet函数的unsigned long类型的值。
函数tasklet_schedule用来调度一个tasklet执行。示比例如以下:
void example_do_tasklet(unsigned long) { …… } DECLARE_TASKLET(example_tasklet, example_do_tasklet, 0); irqreturn_t example_interrupt(int irq, void *dev_id, struct pt_regs *regs) { …… tasklet_schedule(&example_tasklet); …… }
工作队列:工作队列会在将来的某个时间、在某个特定的工作者进程上下文中调用一个函数。由于工作队列函数执行在进程上下文中。因此可在必要时休眠。
工作队列一般程序编写示比例如以下:
static struct work_struct example_workqueue; INIT_WORK(&example_workqueue, (void (*)(void *))example_do_tasklet, NULL); irqreturn_t example_interrupt(int irq, void *dev_id, struct pt_regs *regs) { …… schedule_work(&example_workqueue); …… }
<五>中断共享
安装共享的处理例程:就像普通非共享的中断一样,共享的中断也是通过request_irq安装的,但有两处不同:
1、请求中断时,必须指定flags參数中的SA_SHIRQ位。
2、dev_id參数必须是唯一的。
不论什么指定模块地址空间的指针都能够使用。但dev_id不能设置成NULL。
当请求一个共享中断时,假设满足以下条件之中的一个,那么request_irq就会成功:
1、中断信号线空暇
2、不论什么已经注冊了该中断信号线的处理例程也标识了IRQ是共享的。
使用共享处理例程的驱动程序须要小心一件事情:不能使用enable_irq和disable_irq。