本文转载自:http://blog.chinaunix.net/uid-25014876-id-90740.html

linux设备驱动归纳总结(六):1.中断的实现

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

一、什么是中断

中断分两种:

1)中断,又叫外部中断或异步中断,它的产生是由于外设向处理器发出中断请求。其中外部中断也有两种,这是由配置寄存器设定的:普通中断请求(IRQ)和快速中断请求(FIQ)。一般地,linux下很少使用快速中断请求。

2)异常,又叫内部中断或同步中断,它的产生是由于处理器执行指令出错。

在以下的内容我是要介绍由于外部设备产生的中断。

这里我还有两个名词要说清楚

1)中断请求线:在后面也叫中断号,每个中断都会通过一个唯一的数值来标识,而这个值就称做中断请求线

2)在2440芯片中,位。具体可以查看芯片手册。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

二、什么是中断处理函数

在相应一个中断是,内核会执行该信号对应的一个函数,该函数就叫做该中断对应的中断处理函数。一般来说,中断的优先级是最高的,一但接收到中断,内核就会调用对应的中断处理函数。

中断处理函数运行在中断上下文中。中断上下文与内核上下文有一点区别:

内核上下文是指应用层调用系统调用陷入内核执行,内核代表陷入的进程执行操作。函数中可以通过current查看当前进程(即应用层的进程)的信息,并且可以睡眠。

中断上下文中,不能通过current查看调用它的应用层进程的信息,同时,处于中断上下文时,不能睡眠。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

一、从硬件角度看中断

中断的产生到处理器获得中断这段过程中,还要通过中断处理器来筛选信号。

先温习一下S3C2440芯片手册的知识:中断是如何产生的,中断处理器本身如何处理中断。先看一下一幅经典的图,这是介绍中断控制器的工作流程:

从硬件上的分类,有两种不同的中断类型:

1)自己占有SORCPND寄存器的一位(without sub-register)。

2)几个中断共同享用SRCPND寄存器的一位(with sub-register)。

其实两种都差不多,只是多了两步的检测。我以自己占用一位的中断来举例,如EINT1,在我的开发板,EINT1上接了一个按键。

1)当我按下按键产生电平变化,传到S3C2440的中断控制器上(即将要进入上面图的流程图)。

2)首先,信号要经过寄存器SRCPND,SRCPND是用来配置当前的处理器要接收什么中断,如果该寄存器配置成接收EINT1中断(对应位置一),则允许继续下一步。

3)然后,信号经过寄存器MASK,这是用来设置当前系统需要屏蔽的中断。注意,这里的屏蔽跟上一个寄存器的不接收中断是不一样的。这里的屏蔽是指,中断是接受了,但是由于某种原因,先暂时不屏蔽产生的中断。

4)通过INTPND寄存器,查看当前是否有相同的中断已经被请求(如果是,INTPND对应位置一)。

5)如果没有相同的中断在请求,中断处理器才会把这个信号传给处理器,这时处理器才会知道有EINT0的中断真正来了,要对信号进行处理了。

注:如果设定了EINT0是快速中断模式(FIQ),中断通过SRCPND寄存器后就会通过MODE寄存器的判断,确定是FIQ后,中断控制器优先将该中断传给CPU处理。

6)对应传来的中断类型(IRQ或FIQ),通过CPSR寄存器切换到对应的工作模式(ARM有七种工作模式)。

7)切换工作模式后,进入指定的中断处理入口执行中断处理函数。

步在linux下的实现相对复杂,不像在裸板程序,只需要切换一下工作模式,执行相应的函数就可以了。迟点会介绍linux如何实现。

来个流程图比较只在,同时来个类比,处理器是老板,中断处理器是小秘:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

四、注册和释放中断处理函数

上面的介绍只是讲解了一个设备产生中断后要经过怎么样的步骤才能让处理器接收到中断信号。传入处理器后,接下来的工作就是由内核来实现了,那是一个复杂的机制,我们这里先不说。但是,内核提供了相关的接口给我们,我们只要通过接口告诉内核,当来了指定中断时,内核你该执行哪个中断处理函数。

注册中断处理函数:

/*include */

int request_irq(unsigned int irq, irq_handler_t handler,

unsigned long irqflags, const char *devname, void *dev_id)

使用:

将中断号irq与中断处理函数handler对应

参数:

irq:指定要分配的中断号,中断号的定义在“include/mach/irqs.h”中。注意,不管是单独占有中断请求线的中断,还是共享中断请求线的每个中断,都有一个对应的中断号。,所以,调用该函数不需要考虑是哪种中断(是否共享寄存器),你想哪种中断响应,你就填对应的中断号。

handler:中断处理函数指针。

irqflags:中断处理标记,待会介绍:

devname:该字符串将显示在/proc/irq和/pro/interrupt中。

dev_id:ID 号,待会会介绍。

返回值:成功返回0,失败返回非0。

注册函数需要注意两件事:

1)该函数会睡眠。

2)必须判断返回值。

中断处理标志irqflags,这里先介绍几个待会要用的:

/*linux-2.6.29/include/linux/interrupt.h*/

29 #define IRQF_TRIGGER_NONE 0x00000000

30 #define IRQF_TRIGGER_RISING 0x00000001 //上升沿触发中断

31 #define IRQF_TRIGGER_FALLING 0x00000002 //下降沿触发中断

32 #define IRQF_TRIGGER_HIGH 0x00000004 //高电平触发中断

33 #define IRQF_TRIGGER_LOW 0x00000008 //低电平触发中断

34 #define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \

35 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)

36 #define IRQF_TRIGGER_PROBE 0x00000010

释放中断处理函数:

void free_irq(unsigned int irq, void *dev_id)

编写中断处理函数:

中断处理函数声明如下:

static irqreturn_t intr_handler(int irq, void *dev_id)

先看第一个参数irq,这是调用中断处理函数时传给它的中断号,对于新版本的内核,这个参数已经用处不大,一般只用于打印。

第二个参数dev_id,这个参数与request_irq()的参数dev_id一致,由于待会的程序我并不需要用这个参数,所以先不介绍。

再看返回值,中断处理函数的返回值有三个:

/*linux-2.6.29/include/linux/interrupt..h*/

21 #define IRQ_NONE (0) //如果产生的中断并不会执行该中断处理函数时返回该值

22 #define IRQ_HANDLED (1) //中断处理函数正确调用会返回

23 #define IRQ_RETVAL(x) ((x) != 0) //指定返回的数值,如果非0,返回IRQ_HADLER,否则

26 #ifndef IRQ_NONE //返回IRQ_NONE。

接下来就要写函数了,在我的开发板中,有一个按键是对应EINT1,我要实现的操作是,当我按下按键,终端打印”key down”。在这个程序中,我并没有使用dev_id。这将在会以后的章节介绍。

/*6th_irq_1/1st/test.c*/

1 #include

2 #include

3

4 #include

5

。。。省略。。。

13 irqreturn_t irq_handler(int irqno, void *dev_id) //中断处理函数

14 {

15 printk("key down\n");

16 return IRQ_HANDLED;

17 }

18

19 static int __init test_init(void) //模块初始化函数

20 {

21 int ret;

22

23 /*注册中断处理函数,必须查看返回值

24 * IRQ_EINT1:中断号,定义在"include/mach/irqs.h"中

25 * irq_handler:中断处理函数

26 * IRQ_TIRGGER_FALLING:中断类型标记,下降沿触发中断

27 * ker_INT_EINT1:中断的名字,显示在/proc/interrupts等文件中

28 * NULL;现在我不使用dev_id,所以这里不传参数

29 */

30 ret = request_irq(IRQ_EINT1, irq_handler, IRQF_TRIGGER_FALLING,

31 "key INT_EINT1", NULL);

32 if(ret){

33 P_DEBUG("request irq failed!\n");

34 return -1;

35 }

36 printk("hello irq\n");

37 return 0;

38 }

39

40 static void __exit test_exit(void) //模块卸载函数

41 {

42 free_irq(IRQ_EINT1, NULL);

43 printk("good bye irq\n");

44 }

45

46 module_init(test_init);

47 module_exit(test_exit);

48

49 MODULE_LICENSE("GPL");

50 MODULE_AUTHOR("xoao bai");

51 MODULE_VERSION("v0.1");

接下来验证一下:

[root: 1st]# insmod test.ko

hello irq

[root: 1st]# key down //按下按键显示

key down

key down

key down

[root: 1st]# cat /proc/interrupts

CPU0

17: 11 s3c-ext0 key INT_EINT1 显示我注册和中断名字

30: 423482 s3c S3C2410 Timer Tick

32: 0 s3c s3c2410-lcd

51: 2782 s3c-ext eth0

70: 49 s3c-uart0 s3c2440-uart

71: 69 s3c-uart0 s3c2440-uart

79: 0 s3c-adc s3c2410_action

80: 0 s3c-adc adc, s3c2410_action

83: 0 - s3c2410-wdt

Err: 0

[root: key INT_EINT1]# rmmod test //卸载

good bye irq

[root: key INT_EINT1]# cat /proc/interrupts //卸载后,我的中断名字消失了

CPU0

30: 828977 s3c S3C2410 Timer Tick

32: 0 s3c s3c2410-lcd

51: 3202 s3c-ext eth0

70: 192 s3c-uart0 s3c2440-uart

71: 277 s3c-uart0 s3c2440-uart

79: 0 s3c-adc s3c2410_action

80: 0 s3c-adc adc, s3c2410_action

83: 0 - s3c2410-wdt

Err: 0

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

五、proc/interrupt

接下来,稍稍介绍一下proc/interrupt

[root: 1st]# cat /proc/interrupts

CPU0

17: 11 s3c-ext0 key INT_EINT1 显示我注册和中断名字

首先,第一列是中断号,之前的程序应该有人会有疑问:

中断号在哪里找的?

在S3C2440中,这些中断号定义在文件"include/mach/irqs.h"中,在这里,可以找到对应的中断:

25 /* main cpu interrupts */

26 #define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */

27 #define IRQ_EINT1 S3C2410_IRQ(1)

28 #define IRQ_EINT2 S3C2410_IRQ(2)

29 #define IRQ_EINT3 S3C2410_IRQ(3)

30 #define IRQ_EINT4t7 S3C2410_IRQ(4) /* 20 */

在这里我标了两处红笔:

第一处:可以看到,S3C2440所有的中断号在原来的基值上加了16构成中断号,但不同的芯片或许有不同的定义方法。

第二处:有些中断号是共享的。在S3C2440中,EINT4---EINT7是共享寄存器SRCPND中的一位,所以,linux系统给这样的中断分配了一个共享的中断号。那就是说,如果你使用IRQ_EINT4t7,当收到这些中断时,都会调用对应的中断处理函数。这就需要在中断处理函数中通过第一个传参irq来辨别中断并执行相应的操作。如:

13 irqreturn_t irq_handler(int irqno, void *dev_id) //中断处理函数

14 {

15 switch(irqno){

16 。。。。}

17 }

那肯定有人会说,这太麻烦了吧,有没有更好的办法处理共享中断号?

那当然是有,继续看文件"include/mach/irqs.h":

61 /* interrupts generated from the external interrupts sources */

62 #define IRQ_EINT4 S3C2410_IRQ(32) /* 48 */

63 #define IRQ_EINT5 S3C2410_IRQ(33)

64 #define IRQ_EINT6 S3C2410_IRQ(34)

65 #define IRQ_EINT7 S3C2410_IRQ(35)

66 #define IRQ_EINT8 S3C2410_IRQ(36)

看到了吧?内核把共享的中断分离出来,只要使用这些标记就可以了。

其实上面我只是想说明:无论在硬件上ARM是怎么实现中断的(是否共享),在内核看来所有的中断都是一样的,都可以独自获得一个中断号。

第二列“11”是对应处理器响应该中断的次数。

第三列“s3c-ext0”是处理这个中断的中断控制器

第四列一看就知道调用irq_request()时定义的中断名字。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

六、总结

其实要实现中断,大部分的工作已经给内核包了,我们只需要做的就是告诉内核,当来了什么中断要执行怎么样的函数,这也是今天介绍的重点,其实步骤很简单:

1)调用两个函数:requesr_irq和free_irq。

2)实现中断处理函数:irq_handler()。

还有没讲的知识:

1)还有几个irqflag没介绍。

2)没有介绍dev_id。

可能有人会加载上面的模块失败,这也是我今天没介绍的只是,共享中断号。这里说的共享和硬件的共享不一样性质,下节会介绍。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

linux设备驱动归纳总结(六):1.中断的实现【转】的更多相关文章

  1. linux设备驱动归纳总结(六):3.中断的上半部和下半部——tasklet【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-100005.html linux设备驱动归纳总结(六):3.中断的上半部和下半部——tasklet x ...

  2. linux设备驱动归纳总结(六):2.分享中断号【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-90837.html xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  3. linux设备驱动归纳总结(六):1.中断的实现

    linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  4. 【Linux开发】linux设备驱动归纳总结(六):3.中断的上半部和下半部——工作队列

    linux设备驱动归纳总结(六):3.中断的上半部和下半部--工作队列 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  5. 【Linux开发】linux设备驱动归纳总结(六):1.中断的实现

    linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  6. 【Linux开发】linux设备驱动归纳总结(六):2.分享中断号

    linux设备驱动归纳总结(六):2.分享中断号 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  7. linux设备驱动归纳总结(十二):简单的数码相框【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-116926.html linux设备驱动归纳总结(十二):简单的数码相框 xxxxxxxxxxxxxx ...

  8. linux设备驱动归纳总结(十一):写个简单的看门狗驱动【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-112879.html linux设备驱动归纳总结(十一):写个简单的看门狗驱动 xxxxxxxxxxx ...

  9. linux设备驱动归纳总结(四):5.多处理器下的竞态和并发【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-67673.html linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxx ...

随机推荐

  1. 1.Oracle数据库概述

    Oracle数据库概述 1.1Oracle结构图 1.1如何访问数据库 a.本机直接通过sock(套接字)方式访问  IPC UDP协议 ,不需要网络 b.通过tcp建立连接到oracle服务器 1. ...

  2. WeUI—微信官方UI库

    WeUI 为微信 Web 服务量身设计 概述 WeUI是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信 Web 开发量身设计,可以令用户的使用感知更加统一.包含button.cell ...

  3. Mac自定义隐藏或显示文件的快捷键

    Mac自定义隐藏或显示文件的快捷键 本教程教大家学会自定义隐藏和显示文件夹的快捷键(Command+Shift+.). 1. 打开应用程序--Automator--选择"服务"-- ...

  4. MVC项目实践,在三层架构下实现SportsStore-09,ASP.NET MVC调用ASP.NET Web API的查询服务

    ASP.NET Web API和WCF都体现了REST软件架构风格.在REST中,把一切数据视为资源,所以也是一种面向资源的架构风格.所有的资源都可以通过URI来唯一标识,通过对资源的HTTP操作(G ...

  5. 【Android】ListView监听上下滑动(判断是否显示返回顶部按钮

    设置滚动监听判断ListView的滚动方向同时获取屏幕高度.ListView实际高度,判断是否需要展示返回顶部按钮 package com.zihao.activity;   import java. ...

  6. 在windows下使用cmd命令行对java文件进行编译和执行

    windows下利用cmd命令行可以调用jdk里的javac.exe和java.exe对java文件进行编译和执行,前提是jdk已成功安装并正确配置相关环境变量 相关配置链接:java基础学习总结—— ...

  7. go 语言的库文件放在哪里?如何通过nginx代理后还能正确获取远程地址

    /usr/local/Cellar/go/1.5.1/libexec/src/ 他的RemoteAddr 是从哪里获取? func (c *conn) RemoteAddr() Addr { if ! ...

  8. RequireJS初探

    什么是RequireJS? /* --- RequireJS 是一个JavaScript模块加载器.它非常适合在浏览器中使用, 它非常适合在浏览器中使用,但它也可以用在其他脚本环境, 就像 Rhino ...

  9. Browserify

    NodeJS 把 JavaScript 的使用从浏览器端扩展到了服务器端,使得前端开发人员可以用熟悉的语言编写服务器端代码.这一变化使得 NodeJS 很快就流行起来.在 NodeJS 社区中有非常多 ...

  10. C++Primer 第十九章

    //1.控制内存分配: //A:某些应用程序对内存分配有特殊的需求,因此我们无法将标准内存管理机制直接应用于这些程序.它们常常需要自定义内存分配的细节,比如使用关键字new将对象放置在特定的内存空间中 ...