linux中断处理

13 篇文章 0 订阅
8 篇文章 0 订阅

1 注册中断处理例程

许多设备的内部工作通常是在和处理器完全不同的时间周期里完成的,并且总要比处理器慢,为了不让cpu一直等待外部设备的时间处理,必须引入中断概念。中断,当设备处理完内部工作后需要获取cpu资源时,向cpu发出的信号。该信号的发送依赖于中断信号线,不同的设备需要不同的中断信号线(当然也会有共享中断信号)。内核维护了一个中断信号线的注册表,驱动程序在使用中断前要请求一个中断通道(或IRQ中断请求),在使用结束后释放该通道。下面是请求和释放中断通道的相应接口:

#include <linux/sched.h>
int request_irq(unsigned int irq,
                irqreturn_t (*handler)(int, void *, struct pt_regs *),
                unsigned long flags,
                const char *dev_name,
                void *dev_id);
参数:
unsigned int irq:
请求的中断号

irqreturn_t (*handler):
安装的处理函数指针. 我们在本章后面讨论给这个函数的参数以及它的返回值.

unsigned long flags:
如你会希望的, 一个与中断管理相关的选项的位掩码(后面描述).

const char *dev_name:
这个传递给 request_irq 的字串用在 /proc/interrupts 来显示中断的拥有者(下一节看到)

void *dev_id:
用作共享中断线的指针. 它是一个独特的标识, 用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区(来标识哪个设备在中断). 如果中断没有被共享, dev_id 可以设置为 NULL, 但是使用这个项指向设备结构不管如何是个好主意. 我们将在"实现一个处理"一节中看到 dev_id 的一个实际应用.

flags 中可以设置的位如下:
SA_INTERRUPT
当置位了, 这表示一个"快速"中断处理. 快速处理在当前处理器上禁止中断来执行(这个主题在"快速和慢速处理"一节涉及).
SA_SHIRQ
这个位表示中断可以在设备间共享. 共享的概念在"中断共享"一节中略述.
SA_SAMPLE_RANDOM
这个位表示产生的中断能够有贡献给 /dev/random 和 /dev/urandom 使用的加密池. 这些设备在读取时返回真正的随机数并且设计来帮助应用程序软件为加密选择安全钥. 这样的随机数从一个由各种随机事件贡献的加密池中提取的. 如果你的设备以真正随机的时间产生中断, 你应当设置这个标志. 如果, 另一方面, 你的中断是可预测的( 例如, 一个帧抓取器的场消隐), 这个标志不值得设置 -- 它无论如何不会对系统加密有贡献. 可能被攻击者影响的设备不应当设置这个标志; 例如, 网络驱动易遭受从外部计时的可预测报文并且不应当对加密池有贡献. 更多信息看 drivers/char/random.c 的注释。
返回值:
0:成功
负数:错误码

void free_irq(unsigned int irq, void *dev_id);
参数同上。
/proc接口

当有多个硬件产生中断时,内核需要生成一份表格已记录中断和中断设备的情况,这个表格显示在/proc/interrupts中。

username@ubuntu:~$ cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7
  0:       4384          0          0          0          0          0          0          0   IO-APIC-edge      timer
  1:        950          2          0          1          0          0          0          0   IO-APIC-edge      i8042
  6:          0          0          0          0          0          0          3          0   IO-APIC-edge      floppy
  7:          0          0          0          0          0          0          0          0   IO-APIC-edge      parport0
  8:          1          0          0          0          0          0          0          0   IO-APIC-edge      rtc0
  9:          0          0          0          0          0          0          0          0   IO-APIC-fasteoi   acpi
 12:       3573          0          0          3          0          0          0          0   IO-APIC-edge      i8042
 14:          0          0          0          0          0          0          0          0   IO-APIC-edge      ata_piix
NMI:          0          0          0          0          0          0          0          0   Non-maskable interrupts
LOC:   34453497   34673929   33285941   33955988   26748072   26296607   25414863   26528495   Local timer interrupts
SPU:          0          0          0          0          0          0          0          0   Spurious interrupts
PMI:          0          0          0          0          0          0          0          0   Performance monitoring interrupts
IWI:          0          0          0          0          0          0          0          0   IRQ work interrupts
RES:   12987795   17430673   15115152   14059807   12072005   15228434   13090966   12469424   Rescheduling interrupts
CAL:       2382       2842       2895       2903    6002589       2717       2709       2523   Function call interrupts
TLB:    3183761    3010846    2658535    2572622    3065747    3268356    2652696    2713237   TLB shootdowns
TRM:          0          0          0          0          0          0          0          0   Thermal event interrupts
THR:          0          0          0          0          0          0          0          0   Threshold APIC interrupts
MCE:          0          0          0          0          0          0          0          0   Machine check exceptions
MCP:       4340       4340       4340       4340       4340       4340       4340       4340   Machine check polls
ERR:          0
MIS:          0

第一列是已经安装的中断处理例程的中断号;
第二至九列分别是每个cpu收到的中断数量;
第十列是处理中断的可编程中断控制器信息;
第十一列是注册中断处理例程的设备信息。

还有一个和中断相关的文件:/proc/stat
stat文件每行第一个字符串为该行的关键字,其中intr表示的正是中断相关的信息。
intr后的第一列为接收的中断总数,之后为每个IRQ信号线接收的中断数量。

自动探测IRQ号

驱动程序初始化时,通常需要确定设备将要使用哪条IRQ信号线以便正确的安装处理例程。自动探测IRQ号需要完成的功能是:通知设备产生一个中断信号并观察会发生什么。通常通过两种方法来实现这样的功能:调用内核提供的辅助函数,或者自己实现该功能。

在内核帮助下探测

内核提供了一些底层函数来探测中断号。它只能在非共享中断的情境下工作,但是大部分设备能够在共享中断状态工作,并且提供了更好的方法来尽量发现配置的中断号。

unsigned long probe_irq_on(void);
这个函数返回一个未安排的中断的位掩码. 驱动必须保留返回的位掩码, 并且在后面传递给 probe_irq_off。 在这个调用之后, 驱动应当安排它的设备产生至少一次中断。

int probe_irq_off(unsigned long);
在设备已请求一个中断后, 驱动调用这个函数, 作为参数传递之前由 probe_irq_on 返回的位掩码。probe_irq_off 返回在"probe_on"之后发出的中断号。如果没有中断发生, 返回 0 (因此, IRQ 0 不能探测, 但是没有用户设备能够在任何支持的体系上使用它). 如果多于一个中断发生( 模糊的探测 ), probe_irq_off 返回一个负值。
注意小心使能设备上的中断, 在调用 probe_irq_on 之后启用设备上的中断以及在调用 probe_irq_off 后禁止它们。另外, 在 probe_irq_off 之后需要处理设备上待处理的中断。
实现自己的探测功能

主要实现功能同上:通知设备产生一个中断信号并观察会发生什么。但实现自己的探测功能一般是启用所有未被占用的中断,然后观察会发生什么。
###2 实现中断处理例程
类比定时器,实现中断处理例程的注意事项:
1.不能向用户空间发送和接收数据,因为它不是在任何进程的上下文中执行的;
2.不能做任何可能引发休眠的动作;
3.不能调用schule函数;
中断例程的功能:将有关中断接收的信息反馈给设备,并根据正在服务的中断的不同含义对数据进行相应的读和写。
中断例程的典型任务:如果中断通知进程所等待的时间已经发生,就会唤醒在该设备上的休眠的进程。
####处理例程的参数及返回值
中断处理例程常用的三个参数:
int irq:中断号,用于调试信息输出;
void *dev_id:客户数据类型,一般是驱动程序的私有信息;
struct pt_regs *regs:保存处理器进入中断代码前的处理器上下文快照(很少使用);
例:

//中断处理例程
static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
        struct sample_dev *dev = dev_id;
        /* now `dev' points to the right hardware item */
        /* .... */
}
//和中断处理例程相关联的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 /* flags */, "sample", dev /* dev_id */);
        /*....*/
        return 0;

}

返回值:
用于指明是否真正处理了一个中断,是则返回IRQ_HANDLED,否则返回IRQ_NONE;同时也可同过下面宏来产生返回值:IRQ_RETVAL(handled)。
####启用和禁用中断
禁止和启用单个中断:

 <asm/irq.h>
 void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

禁止和启用所有中断:

<asm/system.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);

3 中断的顶半部和下半部

中断处理的一个主要问题是如何处理耗时任务。
Linux (许多其他系统一起)通过将中断处理分为 2 部分解决这个问题。实际响应中断的例程,称为顶半部,也就是用request_irq注册的终端例程。底半部,被顶半部调度,并在稍后安全时间内执行的例程。
linux有两种机制来实现底半部的处理:tasklet和工作队列。tasklet非常快,但是它的代码必须是原子的;工作队列具有更高的延迟,但允许休眠。这两种机制都在《时间、延迟和延缓操作》中讲到,这里略过。

4 中断共享

安装共享处理例程

共享中断通过 request_irq 来安装就像不共享的一样, 但是有 2 个不同:
1.当请求中断时,必须在 flags 参数中指定,SA_SHIRQ 位。
2.dev_id 参数必须是唯一的。任何模块地址空间的指针都行, 但是 dev_id 明确地不能设置为 NULL。
内核保持着一个与中断相关联的共享处理者列表, 并且 dev_id 可认为是区别它们的签名. 如果 2 个驱动要在同一个中断上注册 NULL 作为它们的签名, 在卸载时事情可能就乱了, 在中断到的时候引发内核 oops. 由于这个理由, 如果在注册共享中断时传给了一个 NULL dev_id , 现代内核会大声抱怨. 当请求一个共享的中断, request_irq 成功, 如果下列之一是真:
1.中断线空闲。
2.所有这条线的已经注册的处理者也指定共享这个 IRQ。
释放处理例程:free_irq。
注意:不能使用enable_irq和disable_irq。如果使用了,共享中断信号线的其他设备就无法正常工作了。

5 中断驱动的I/O

如果与驱动程序管理的硬件之间的数据传输因为某种原因而延迟, 驱动编写者应当实现缓冲。数据缓存有助于将数据的传送和接收与系统调用read和write分离开来,从而提高系统的性能。

一个好的缓存机制需采用中断驱动的 I/O,一个输入缓冲在中断时填充并且被读取设备的进程清空; 一个输出缓存由写设备的进程填充并且在中断时清空。
为使中断驱动的数据传送成功发生, 硬件应当使用下列语义产生中断 :
1.对于输入, 当新数据到达时, 并且准备好被系统处理器获取时,设备中断处理器。实际动作依赖设备使用的是I/O 端口, 内存映射, 还是 DMA。
2.对于输出, 或者当它准备好接受新数据, 或者确认一个成功的数据传送时,设备递交一个中断。 内存映射的和能DMA的设备常常产生中断来告诉系统它们完成了这个缓存。

6 使用流程

MTK平台:

1.确认dts中相应设备的节点
2.将dct中gpio栏相应的GPIO设置为中断模式
3.将dct中eint栏相应的中断号选择为相应的dts中的节点
4.见通用平台的第4步

通用平台:

1.确认dts中相应设备的节点
2.将dts中相应的GPIO设置为中断模式
3.将dts中的设备节点中加入以下代码:

node{
	....
	interrupt-parent = <&eintc>;
    interrupts = <126 IRQ_TYPE_LEVEL_LOW>;
    debounce = <126 0>;
    status = "okay";
    ....
}

4.通过如下代码在源码中获取中断号并注册、使能、禁用中断

       node = of_find_matching_node(node, fp_of_match);//设备节点的of_device_id
        if (node){
                fp_detect_irq = irq_of_parse_and_map(node, 0);
                printk("fp_irq number %d\n", fp_detect_irq);
                if (int_mode == EDGE_TRIGGER_RISING){
                        ret = request_irq(fp_detect_irq,
                                fp_interrupt_edge, IRQF_TRIGGER_RISING,
                                "fp_detect-eint", NULL);
                }
                else if(int_mode == LEVEL_TRIGGER_LOW) {
                        ret = request_irq(fp_detect_irq,fp_interrupt_level, 8, "fp_detect-eint", NULL);
                }
                if (ret > 0){
                        printk("fingerprint request_irq IRQ LINE NOT AVAILABLE!.");
                }
        }else{
                printk("fingerprint request_irq can not find fp eint device node!.");
        }

        enable_irq(fp_detect_irq);

		disable_irq(fp_detect_irq);

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值