Linux设备驱动的中断处理

Linux设备驱动中,中断处理非常重要,尤其是在嵌入式系统中,无时无刻不在与中断打交道,因此,中断处理必须要牢牢掌握。

设备在产生某个事件时通知处理器的方法就是中断。中断就是一个信号,当硬件需要通知CPU你该处理我的数据或状态时,就会发出这个信号,而处理器如果发现驱动注册了该中断信号,就会保存现场,执行中断处理程序,然后再恢复现场。中断处理程序和其他代码是并行的,因此必须考虑竞态问题。

1.注册中断

内核维护了一个中断信号线的注册表,驱动模块在使用中断前要先请求一个中断通道(或者 IRQ中断请求),并在使用后释放它。

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);

中断处理例程可在驱动初始化时或在设备第一次打开时安装。推荐在设备第一次打开、硬件被告知产生中断前时申请中断,因为可以共享有限的中断资源。这样调用 free_irq 的位置是设备最后一次被关闭、硬件被告知不用再中断处理器之后。但这种方式的缺点是必须为每个设备维护一个打开计数。

i386 和 x86_64 体系定义了一个函数来查询一个中断线是否可用:

int can_request_irq(unsigned int irq, unsigned long flags); /*当能够成功分配给定中断,则返回非零值。但注意,在 can_request_irq 和 request_irq 的调用之间给定中断可能被占用*/

快速中断和慢中断
在现代内核中,快速和慢速中断的区别已经消失,剩下的只有一个:快速中断(使用 SA_INTERRUPT )执行时禁止所有在当前处理器上的其他中断(其他处理器仍然可以响应中断)。除非你充足的理由在禁止其他中断情况下来运行中断处理例程,否则不应当使用SA_INTERRUPT。这个在嵌入式系统中或许用的比较多。

 /proc/interrupts:记录了中断信息统计
/proc/stat:记录了系统活动的底层统计信息

2.自动检测 IRQ 号

驱动初始化时最迫切的问题之一是决定设备要使用的IRQ 线,驱动需要信息来正确安装处理例程。自动检测中断号对驱动的可用性来说是一个基本需求。有时自动探测依赖一些设备具有的默认特性,以下是典型的并口中断探测程序:

if (short_irq < 0) /* 依靠使并口的端口号,确定中断*/
 switch(short_base) {
 case 0x378: short_irq = 7; break;
 case 0x278: short_irq = 2; break;
 case 0x3bc: short_irq = 5; break;
 }

有 2 种方法来进行探测中断: 调用内核定义的辅助函数和DIY探测。
(1)调用内核定义的辅助函数

unsigned long probe_irq_on(void); /*这个函数返回一个未分配中断的位掩码。驱动必须保留返回的位掩码, 并在后面传递给 probe_irq_off。在调用probe_irq_on之后, 驱动应当安排它的设备产生至少一次中断*/
int probe_irq_off(unsigned long); /*在请求设备产生一个中断后, 驱动调用这个函数, 并将 probe_irq_on 返回的位掩码作为参数传递给probe_irq_off。probe_irq_off 返回在"probe_on"之后发生的中断号。如果没有中断发生, 返回 0 ;如果产生了多次中断,probe_irq_off 返回一个负值*/

(2)DIY探测
DIY探测与前面原理相同: 使能所有未使用的中断, 接着等待并观察发生什么。

3.实现中断处理例程

中断处理例程唯一的特别之处在中断时运行,它能做的事情受到了一些限制:
(1)中断处理例程不能与用户空间传递数据, 因为它不在进程上下文执行;
(2)中断处理例程也不能做任何可能休眠的事情, 例如调用 wait_event, 使用除 GFP_ATOMIC 之外任何东西来分配内存, 或者锁住一个信号量;
(3)处理者不能调用schedule()。

4.禁用中断

(1)禁用单个中断

void disable_irq(int irq);/*禁止给定的中断, 并等待当前的中断处理例程结束。如果调用 disable_irq 的线程持有任何中断处理例程需要的资源(例如自旋锁), 系统可能死锁*/
void disable_irq_nosync(int irq);/*禁止给定的中断后立刻返回(可能引入竞态)*/
void enable_irq(int irq);

(2)禁用全部中断

void local_irq_save(unsigned long flags);/*在保存当前中断状态到 flags 之后禁止中断*/
void local_irq_disable(void);/* 关闭中断而不保存状态*/

如果调用链中有多个函数可能需要禁止中断, 应使用 local_irq_save

void local_irq_restore(unsigned long flags); /*打开中断使用*/
void local_irq_enable(void);

在 2.6 内核, 没有方法全局禁用整个系统上的所有中断

5.中断顶半部和底半部

中断处理需要很快完成,可通过将中断处理分为两部分来解决这个问题:
(1)顶半部,立即执行,实际响应中断的例程(request_irq 注册的那个例程)
(2)底版本,被顶半部调度,并在稍后更安全的时间内执行的函数
Linux 内核有 2 个不同的机制可用来实现底半部处理:
(1)tasklet (首选机制),它非常快, 但是所有的 tasklet 代码必须是原子的。
(2)工作队列, 它可能有更高的延时,但允许休眠,执行在进程上下文中,但不允许与用户空间进行数据拷贝。

6.中断共享

Linux 内核支持在所有总线上中断共享,Linux大部分中断都是可以共享的。
在通过request_irq 安装处理例程时候,有些不同:
(1)当request_irq 时,flags 中必须指定SA_SHIRQ 位;
(2)dev_id 必须唯一。任何指向模块地址空间的指针都行,但 dev_id 绝不能设置为NULL。
请求一个共享的中断时,如果满足下列条件之一,则request_irq 成功:
(1)中断线空闲;
(2)所有已经注册该中断信号线的处理例程也标识了IRQ是共享。

一个共享的处理例程必须能够识别自己的中断,并且在自己的设备没有被中断时快速退出(返回 IRQ_NONE )。共享处理例程没有探测函数可用,但使用的中断信号线是空闲时标准的探测机制才有效。一个使用共享处理例程的驱动需要小心:不能使用 enable_irq 或 disable_irq,否则,对其他共享这条线的设备就无法正常工作了。即便短时间禁止中断,另一设备也可能产生延时而为设备和其用户带来问题,因为这个中断可能不是你一个驱动模块在关注。

7.中断驱动的 IO

当与驱动程序管理的硬件间的数据传送可能因为某种原因而延迟,驱动编写者应当实现缓存。
输入:当新数据到达时并且处理器准备好接受时,设备中断处理器。
输出:当设备准备好接受新数据或确认一个成功的数据传送时,设备产生中断。

参考:
https://blog.csdn.net/tigerly/article/details/22874091

Linux设备驱动的中断处理

上一篇:linux 实现双网卡绑定单个IP——team篇


下一篇:学习linux到底有没捷径?讲讲我重入Linux江湖