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

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

转自:http://blog.chinaunix.net/uid-25014876-id-90837.html

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

上一节介绍的内容是,调用接口request_irq(),使中断号与中断处理函数对应。但是,有时候会有这样的情况,如果开发板上按键的中断已经被另外的驱动程序注册中断了,而我现在又想再注册一次这个中断,这就出现了一个中断号不止对应一个中断函数的情况。注意,这里与硬件上的共享中断不一样,这里是指,当一个中断信号来了,基于操作系统,一个中断的到来可以调用多个中断处理程序,与硬件无关。

接下来从错误的代码开始讲解。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

一、错误的产生

以下的代码在“6th_irq_2/1st”中。

假设有这样的情况,有一个人,加载了模块test.ko,模块注册了中断号EINT1。接着,我编写代码(我并不知道中断号已经被使用了),加载模块test1.c(在err目录下),模块同样注册了中断号EINT1。但这样的注册不成功的。

看效果:

[root: 1st]# insmod test.ko //某处加载第一个时成功

hello irq

[root: 1st]# key down

key down

[root: 1st]# insmod err/test1.ko //假设我并不知道已经记载了一次,加载第二次时失败

[test_init]request irq failed!

insmod: cannot insert ‘err/test1.ko‘: Device or resource busy

[root: 1st]# cat /proc/interrupts //查看proc时发现,原来早就有人注册了EINT1中断号

CPU0

17: 2 s3c-ext0 key INT_EINT1

30: 20429 s3c S3C2410 Timer Tick

32: 0 s3c s3c2410-lcd

51: 3032 s3c-ext eth0

70: 252 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

这个就是两男争一妞的情况了,解决办法有两个:

1、动物界的规矩,干掉其中一个,谁赢谁说了算。

2、邪恶做法,和平解决,实现共享。

第一个解决办法很简单,查阅内核代码,找到注册该中断的模块,并且想办法卸载该模块。但是,如果那个模块实在是太重要的,不能卸载,那只能共享了。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

二、共享中断号的标记

在上一节的内容,注册中断处理函数接口request_irq()的参数irqflags还没完全介绍完毕,除了IRQ_TIRGGER_FALLING这类指明中断触发的条件的标志外,下面还介绍三个:

SA_INTERRUPT:这个标志表明该中断处理程序是一个快速中断处理程序。过去,linux系统会区分快速我慢速中断,但现在这个标志只有这样的效果:当响应这个中断时,禁止所有的中断,是该中断处理函数不会被其他中断打断,迅速执行。

SA_SAMPLE_RANDOM:这个标志表明产生的中断会对内核的entropy pool有贡献。Entropy pool负责产生随机数。

SA_SHIRQ:这个标志表明多个中断处理程序可以共享一个中断号。

相对其他两个,SA_SHIRQ是常用的标记。也就是说,我的中断注册失败,原因是我没有共享标记。也就是说,我需要在我的注册中断函数添加共享标记。但再回想一下两男争一妞的场景,需要共享前提是两个男的都同意共享,所以,原来的中断注册函数也需要共享标记。需要修改原来的函数test.c和我新写的test1.c,都加上共享标记SA_SHIRQ,表示它们两都同意共享。

在ARM下SA_SHIRQ相当于标志IRQF_SHARED:

/*iclude/linux/interrupt.h*/

53 #define IRQF_DISABLED 0x00000020 //SA_INTERRUPT

54 #define IRQF_SAMPLE_RANDOM 0x00000040 //SA_SAMPLE_RANDOM

55 #define IRQF_SHARED 0x00000080 //SA_SHIRQ

看修改后的代码:

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

30 ret = request_irq(IRQ_EINT1, irq_handler,

31 IRQF_TRIGGER_FALLING | IRQF_SHARED, "key INT_EINT1", NULL);

32 if(ret){

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

34 return ret;

35 }

另外一个一模一样

/*6th_irq_2/1st/err/test1.c*/

30 ret = request_irq(IRQ_EINT1, irq_handler,

31 IRQF_TRIGGER_FALLING | IRQF_SHARED, "key INT_EINT1", NULL);

32 if(ret){

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

34 return ret;

35 }

再加载一次,发现还是不行。

[root: /]# cd review_driver/6th_irq/6th_irq_2/2nd/

[root: 2nd]# insmod test.ko //加载第一个时就已经不行了

[test_init]request irq failed!

insmod: cannot insert ‘test.ko‘: invalid parameter //提示参数错误

[root: 2nd]# insmod err/test1.ko

[test_init]request irq failed!

insmod: cannot insert ‘err/test1.ko‘: invalid parameter

那到底是哪个参数错了?

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

三、设备号ID——dev_id

话说两个难得已经同意共享,但为什么还是只能加载一个呢?女的说:“你们是同意了,但我不能分辨不你们吖!”

中断函数同时一样的道理,实现共享中断号的情况下,在调用free_irq()时,通过对应的标记,内核才会知道该释放哪个中断处理函数。

此时,最有一个没讲的参数dev_id就有他的用处了——内核通过dev_id对应中断处理函数handler。另外,也可以通过它来传参给中断处理函数。

再次修改两个程序,给每个程序就加上一个不同的dev_id:

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

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

14 {

15 printk("key down, dev_id[%d]\n", *(int *)dev_id);

16 return IRQ_HANDLED;

17 }

18

19 int id = 321;

20

。。。。

32 ret = request_irq(IRQ_EINT1, irq_handler,

33 IRQF_TRIGGER_FALLING | IRQF_SHARED, "key INT_EINT1", &id);

。。。。

44 free_irq(IRQ_EINT1, &id);

另外一个一模一样

6th_irq_2/1st/err/test.c

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

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

14 {

15 printk("hello xiaobai!, dev_id[%d]\n", *(int *)dev_id);

16 return IRQ_HANDLED;

17 }

18

19 int id = 123;

20

。。。。

32 ret = request_irq(IRQ_EINT1, irq_handler,

33 IRQF_TRIGGER_FALLING | IRQF_SHARED, "key INT_EINT1", &id);

。。。。

44 free_irq(IRQ_EINT1, &id);

验证一下,共享成功!

[root: 3rd]# insmod test.ko //加载第一个成功

hello irq

[root: 3rd]# key down, dev_id[321]

key down, dev_id[321]

key down, dev_id[321]

[root: 3rd]# insmod err/test1.ko //加载第二个也成功

hello irq

[root: 3rd]# key down, dev_id[321] //当我按下按键时,两个中断处理函数都调用了。

hello xiaobai!, dev_id[123]

key down, dev_id[321]

hello xiaobai!, dev_id[123]

key down, dev_id[321]

hello xiaobai!, dev_id[123]

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

四、介绍完了中断接口函数,下面简单讲一下一个中断产生后的流程:

以下的图是《linux内核设计与实现》上的插图,基于x86体系的,所以有些函数我在ARM下找不到。

先看前三步,这三步是我在《linux设备驱动归纳总结(六):1.中断的实现》的“从硬件角度看中断”有详细描述,当硬件(Hardware)产生中断,传给中断处理器(Interrupt controller),经过中断处理器的一轮筛选后,把中断传给处理器(Processer)。

接下来就要讲解处理器接收到中断之后干什么:

1、处理器中断内核(processor interrupts the kernel):这步骤如下:

1.1、处理器会立即停止它正在进行的事情。

1.2、处理器关闭中断。

1.3、保存原来寄存器的值(这些值属于被中断的任务),切换工作模式至IRQ(S3C2440有7种工作模式,芯片手册有讲解,切换工作模式之前需要保存原来部分寄存器上的值,具体请看S3C2440芯片手册)。

2、do_IRQ:(这个部分说得可能有错,因为我把内核的源代码仔细看过,这部分主要是为了引出下一个的函数handle_IRQ_event())

在ARM相关的内核代码我没找到do_IRQ函数,但我找到一个相关的——asm_do_IRQ。看看大概做了些什么事情:

2.1、把中断号和一个在内核中存放对应的相关数据的结构体(这个结构体就是存放处理器中寄存器的值)作为参数,传参给asm_do_IRQ。

/*linux-2.6.29/arch/arm/kernel/entry-armv.S*/

29 .macro irq_handler

30 get_irqnr_preamble r5, lr

31 1: get_irqnr_and_base r0, r6, r5, lr

32 movne r1, sp

33 @

34 @ routine called with r0 = irq number, r1 = struct pt_regs *

35 @ //获得中断号和一个结构体,作为参数传给asm_do_IRQ

36 adrne lr, 1b

37 bne asm_do_IRQ

2.2、asm_do_IRQ进行一系列的准备工作之后,调用函数generic_handle_irq():

/*linux-2.6.29/arch/arm/kernel/irq.c*/

112 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

113 {

114 struct pt_regs *old_regs = set_irq_regs(regs); //保存并设置寄存器上的值,还没弄懂操作的原因

115

116 irq_enter(); //一系列准备操作,没细看

117

118 /*

119 * Some hardware gives randomly wrong interrupts. Rather

120 * than crashing, do something sensible.

121 */

122 if (irq >= NR_IRQS)

123 handle_bad_irq(irq, &bad_irq_desc);

124 else

125 generic_handle_irq(irq); //调用该函数,开始处理中断。

126

127 /* AT91 specific workaround */

128 irq_finish(irq);

129

130 irq_exit();

131 set_irq_regs(old_regs);

132 }

2.3、generic_handle_irq中调用函数_do_IRQ:

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

309 static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)

310 {

311 #ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ

312 desc->handle_irq(irq, desc);

313 #else

314 if (likely(desc->handle_irq))

315 desc->handle_irq(irq, desc);

316 else

317 __do_IRQ(irq);

318 #endif

319 }

320

321 static inline void generic_handle_irq(unsigned int irq)

322 {

323 generic_handle_irq_desc(irq, irq_to_desc(irq));

324 }

2.4、在__do_IRQ中,会判断该中断号是否已经注册了中断处理函数,如果没有则退出中断,切换至原来的工作模式。如果有,__do_IRQ会调用函数handle_IRQ_event()。

3、irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) 干了些什么:

上面说的内容可能都不太详细,因为我也不太明白有些函数的作用和具体的内核代码,但下面的函数大家应该都会看得懂:

/*linux-2.6.29/kernel/irq/handle.c*/

326 irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)

327 {

328 irqreturn_t ret, retval = IRQ_NONE;

329 unsigned int status = 0;

330

331 if (!(action->flags & IRQF_DISABLED)) //在切换工作模式时内核是禁止了中断的,如果

332 local_irq_enable_in_hardirq(); //注册时使用标记IRQF_DISABLED,则开启中断

333

334 do {

335 ret = action->handler(irq, action->dev_id); //调用我们注册的中断处理函数

336 if (ret == IRQ_HANDLED)

337 status |= action->flags;

338 retval |= ret;

339 action = action->next;

340 } while (action); //这是个循环,那就说,如果我们使用的IRQF_SHARED标识,

341 //它会轮流执行该中断号对应的所有注册的中断处理函数

342 if (status & IRQF_SAMPLE_RANDOM) //如果使用该标记时相应的操作

343 add_interrupt_randomness(irq);

344 local_irq_disable(); //再次关上中断

345

346 return retval;

347 }

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

五、总结

总结一下中断注册的几个注意事项:

1、调用request_irq必须通过返回值判断是否成功。

2、共享中断号时,所有共享这个中断号的request_irq都必须添加标记IRQF_SHARED,另外还需要使用一个独特的设备好dev_id,让内核能够通过dev_id对应注册时的中断处理函数。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

源代码: 6th_irq_2.rar

时间: 2024-11-07 19:41:24

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

linux设备驱动归纳总结

前言: (总结已经基本写完,这段时间我会从新排版和修正.错误总会有的,望能指正!) 前段时间学习了嵌入式驱动,趁着没开始找工作,这段时间我会每天抽出时间来复习. 我的总结是根据学习时的笔记(李杨老师授课).<linux内核设计与实现>第三版.<linux设备驱动程序>第三版和<linux设备驱动开发详解>第一版来归纳的.文章中涉及一些自己的想法,并不能保证所说的一定正确. 我也是一位linux初学者,在这里发博也是想跟大家分享技术,同时也希望别人能够指正错误. 我把一些

linux设备驱动归纳总结(四):5.SMP下的竞态和并发

linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 这节将在上一节的基础上介绍支持多处理器和内核抢占的内核如何避免并发.除了内核抢占和中断外,由于多处理起的缘故,它可以做到多个程序同时执行.所以,进程除了要防自己的处理器外,还要防别的处理器,这个就是这节要介绍的内容. xxxxxxxxxxxxxxxxxxxxxxx

linux设备驱动归纳总结(一)内核的相关基础概念【转】

本文转载自:http://blog.chinaunix.net/uid-25014876-id-59413.html linux设备驱动归纳总结(一):内核的相关基础概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 一.linux设备驱动的作用 内核:用于管理软硬件资源,并提供运行环境.如分配4G虚拟空间等. linux设备驱动:是连接硬件和内核之间的桥梁. linu

linux设备驱动归纳总结(三):5.阻塞型IO实现【转】

本文转载自:http://blog.chinaunix.net/uid-25014876-id-60025.html linux设备驱动归纳总结(三):5.阻塞型IO实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 一.休眠简介: 进程休眠,简单的说就是正在运行的进程让出CPU.休眠的进程会被内核搁置在在一边,只有当内核再次把休眠的进程唤醒,进程才会会重新在CPU运行

linux设备驱动归纳总结(三):6.poll和sellct【转】

本文转载自:http://blog.chinaunix.net/uid-25014876-id-61749.html linux设备驱动归纳总结(三):6.poll和sellct xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 接下来会讲系统调用select在驱动中的实现,如果对系统调用select不太懂的话,建议先看书补习一下. xxxxxxxxxxxxxxxxxxxx

linux设备驱动归纳总结(三):7.异步通知fasync【转】

本文转载自:http://blog.chinaunix.net/uid-25014876-id-62725.html linux设备驱动归纳总结(三):7.异步通知fasync xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 异步通知fasync是应用于系统调用signal和sigaction函数,下面我会使用signal函数.简单的说,signal函数就是让一个信号与与

linux设备驱动归纳总结(七):1.时间管理与内核延时【转】

本文转载自:http://blog.chinaunix.net/uid-25014876-id-100005.html linux设备驱动归纳总结(七):1.时间管理与内核延时 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 这节将介绍一些很枯燥的内核,大体是内核中时间的概念和内核延时的使用,并没有源代码. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现【转】

本文转自自:http://blog.chinaunix.net/uid-25014876-id-59418.html linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现 一.结构体struct file和struct inode 在之前写的函数,全部是定义了一些零散的全局变量.有没有办法整合成到一个结构体当中?这样的话,看起来和用起来都比较方便.接下来就要说这方面的问题. 不过先要介绍一下除了fops以外的两个比较重要的结构体: 1)struct file 在内核中,f

linux设备驱动归纳总结(三):6.poll和sellct

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 接下来会讲系统调用select在驱动中的实现,如果对系统调用select不太懂的话,建议先看书补习一下. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 一.系统调用select的简介 简单来说,se