request_irq 中断号

本文详细解析了S3C2410处理器的中断处理过程,包括中断号的获取方式、中断描述符表的初始化以及特定中断号的修正逻辑。通过实例展示了如何正确配置和使用中断。

上面在说到安装中断的时候说过,调用request_irq()时的参数中irq的确定是个难题,为什么?

你如果到网络上查一下关于linux的资料,十有八九是关于i386体系结构上的,但linux是可以运行在多种cpu上的,比如采用arm内核的s3c2410,在i386体系上的经验在这里可以用么?我们试验一下:硬件准备,使用s3c2410的EINT0引脚作中断测试,为它编写一个中断驱动程序,最后将面临中断的安装,调用intrequest_irq(unsignedintirq,void (*handler)(int,void *,structpt_regs *),unsignedlongirq_flags,constchar *devname,void *dev_id),其余都好说,irq取什么值?很容易查到类似信息:在i386体系结构中,LINUX内核对中断向量号的使用规定如下:

       0-31号中断向量对应于异常类型,32-47号中断向量分配给可屏蔽硬件中断,48-255号分配给软件中断。所以中断控制器上的IRQ0对应于中断向量32。

       在ARM的手册上,所有的中断都叫异常,在S3C2410的手册上,其中断控制器的外部中断都叫EINTn,看起来EINT0就像是IRQ0 。

       那我们不如试试吧,按照经验,取irq=32,request_irq返回一个值,乃是-EINVAL,意思是说irq的取值非法,要么试试其他值,反正都不能正常工作。

怎么办?让我们寻根求源吧!

       文章开头说过:“当中断系统硬件产生一个中断信号,LINUX的中断处理系统将根据从硬件获得的中断号调用用户编写的中断处理程序”,我们看一下linux是如何取得中断号的过程,从中寻觅硬件的中断号如何对应到中断安装的系统调用中的参数的数值。中断产生后cpu进入中断向量指向的总的中断入口程序,对于arm的cpu来说,这个程序在arch/arm/kernel/entry-armv.S中的这部分:

vector_IRQ: @

  @savemodespecificregisters

  @

 ldrr13, .LCsirq

 sublr,lr, #4

 strlr, [r13]   @savelr_IRQ

 mrslr,spsr

 strlr, [r13, #4]   @savespsr_IRQ

  @

  @nowbranchtothereleventMODEhandlingroutine

  @ 

 movr13, #I_BIT |MODE_SVC

 msrspsr_c,r13   @switchtoSVC_32mode

 

 andlr,lr, #15

 ldrlr, [pc,lr,lsl #2]

 movspc,lr    @Changesmodeandbranches

 

.LCtab_irq: .word __irq_usr   @ 0  (USR_26 /USR_32)

  .word __irq_invalid   @ 1  (FIQ_26 /FIQ_32)

  .word __irq_invalid   @ 2  (IRQ_26 /IRQ_32)

  .word __irq_svc   @ 3  (SVC_26 /SVC_32)

  .word __irq_invalid   @ 4

  .word __irq_invalid   @ 5

  .word __irq_invalid   @ 6

  .word __irq_invalid   @ 7

  .word __irq_invalid   @ 8

  .word __irq_invalid   @ 9

  .word __irq_invalid   @ a

  .word __irq_invalid   @ b

  .word __irq_invalid   @ c

  .word __irq_invalid   @ d

  .word __irq_invalid   @ e

  .word __irq_invalid   @ f

 

  .align5

       根据注释并仔细研究这部分可知,中断产生后,程序保存一些于中断相关的特殊寄存器,改变cpu模式为svc模式并转到__irq_usr,这部分代码如下:

__irq_usr:subsp,sp, #S_FRAME_SIZE

 stmiasp, {r0 -r12}   @saver0 -r12

 ldrr4, .LCirq

 addr8,sp, #S_PC

 ldmiar4, {r5 -r7}   @getsavedPC,SPSR

 stmiar8, {r5 -r7}   @savepc,psr,old_r0

 stmdbr8, {sp,lr}^

 alignment_trapr4,r7, __temp_irq

 zero_fp

1: get_irqnr_and_baser0,r6,r5,lr

 movner1,sp

 adrsvcne,lr,1b

  @

  @routinecalledwithr0 =irqnumber,r1 =structpt_regs *

  @

 bnedo_IRQ

 movwhy, #0

 get_current_tasktsk

 bret_to_user

       注意get_irqnr_and_baser0,r6,r5,lr一句,这是一个宏,顾名思义,它将取得中断号,将这个宏展开,对于s3c2410来说,是其中的这部分代码:

#elifdefined(CONFIG_ARCH_S3C2410)

#include <asm/hardware.h>

 

  .macro disable_fiq

  .endm

 

  .macro get_irqnr_and_base,irqnr,irqstat,base,tmp

 movr4, #INTBASE  @virtualaddressofIRQregisters

 

 ldr /irqnr, [r4, #0x8] @readINTMSK

 ldr /irqstat, [r4, #0x10]   @readINTPND

 

 bics    /irqstat, /irqstat, /irqnr

 bics    /irqstat, /irqstat, /irqnr

 beq1002f

 

 mov /irqnr, #0

1001: tst /irqstat, #1

 bne1002f   @foundIRQ

 add /irqnr, /irqnr, #1

 mov /irqstat, /irqstat,lsr #1

 cmp /irqnr, #32

 bcc1001b

1002:

  .endm

 

  .macro irq_prio_table

  .endm  

       分析这段代码可知,这个宏取得中断悬挂寄存器的值,将它转换为中断位对应的数字值即EINTn对应n并返回到第一个参数中,比如EINT0上发生中断,在宏的第一个参数中返回0,EINT1上发生中断,在宏的第一个参数中返回1。

       再返回头来看__irq_usr的代码,根据注释和分析可知,代码在r0中存储irq号,r1指向一个包含发生中断时各种寄存器值的结构pt_regs,然后专向执行do_IRQ,函数do_IRQ()的代码在arch/arm/kernel/irq.c中:

asmlinkagevoiddo_IRQ(intirq,structpt_regs *regs)

{

structirqdesc *desc;

structirqaction *action;

intcpu;

 

irq =fixup_irq(irq);

 

 ……

 ……

}

       注意这个函数中的irq =fixup_irq(irq);一句,他对刚才取得的irq值进行了修正,对于s3c2410,这个动作在arch/arm/mach-s3c2410/irq.c中:

unsignedintfixup_irq(intirq) {

   unsignedintret;

   unsignedlongsub_mask,ext_mask;

 

   if (irq ==OS_TIMER)

     returnirq;

 

   switch (irq) {

   caseIRQ_UART0:

     sub_mask =SUBSRCPND & ~INTSUBMSK;

     ret =get_subIRQ(sub_mask,0,2,irq);

     break;

   caseIRQ_UART1:

     sub_mask =SUBSRCPND & ~INTSUBMSK;

     ret =get_subIRQ(sub_mask,3,5,irq);

     break;

   caseIRQ_UART2:

     sub_mask =SUBSRCPND & ~INTSUBMSK;

     ret =get_subIRQ(sub_mask,6,8,irq); 

     break;

   caseIRQ_ADCTC:

     sub_mask =SUBSRCPND & ~INTSUBMSK;

     ret =get_subIRQ(sub_mask,9,10,irq);

     break;

   caseIRQ_EINT4_7:

     ext_mask =EINTPEND & ~EINTMASK;

 

 

     ret =get_extIRQ(ext_mask,4,7,irq);

     break;

   caseIRQ_EINT8_23:

     ext_mask =EINTPEND & ~EINTMASK;

     ret =get_extIRQ(ext_mask,8,23,irq);

     break;

   default:

     ret =irq;

    }

 

   returnret;

}

       可见,对于EINT0,并不进行修正,而只对几个特殊中断号和多个中断脚复用一个中断号的情况进行修正。由此,我们可以得出S3C2410中,外部中断脚和中断号的对应关系是:

中断脚位                  中断号

INT_ADC       [31]

INT_RTC       [30] 

INT_SPI1       [29]

INT_UART0     [28]

INT_IIC         [27] 

INT_USBH      [26] 

INT_USBD      [25] 

Reserved        [24]

INT_UART1      [23] 

INT_SPI0        [22] 

INT_SDI         [21] 

INT_DMA3       [20]

INT_DMA2       [19]

 

INT_DMA1       [18] 

INT_DMA0       [17]

INT_LCD        [16] 

INT_UART2      [15]

INT_TIMER4     [14] 

INT_TIMER3     [13]

INT_TIMER2     [12] 

INT_TIMER1     [11] 

INT_TIMER0     [10]

INT_WDT        [9] 

INT_TICK        [8]

nBATT_FLT      [7] 

Reserved        [6] 

EINT8_23        [5] 

EINT4_7         [4] 

EINT3           [3]

EINT2           [2]

EINT1  

         [1]

EINT0           [0]

       那么,在前面的例子中,我们使用irq=0申请中断为什么也不行呢?我们来看一下系统是如何初始化中断数据的,对于s3c2410,系统在初始化中要调用下面的函数:

 

void __inits3c2410_init_irq(void) {

   intirq;

 

   request_resource(&iomem_resource, &irq_resource);

   request_resource(&iomem_resource, &eint_resource);

 

    /*disableallIRQs */

   INTMSK =0xffffffff;

   INTSUBMSK =0x7ff;

   EINTMASK =0x00fffff0;

 

    /*allIRQsareIRQ,notFIQ

      0 :IRQmode

      1 :FIQmode

    */

   INTMOD =0x00000000;

 

    /*clearSource/InterruptPendingRegister */

   SRCPND =0xffffffff;

   INTPND =0xffffffff;

   SUBSRCPND =0x7ff;

   EINTPEND =0x00fffff0;

 

    /*Defineirqhandler */

   for (irq=0;irq <NORMAL_IRQ_OFFSET;irq++) {

     irq_desc[irq].valid =1;

     irq_desc[irq].probe_ok =1;

     irq_desc[irq].mask_ack =s3c2410_mask_ack_irq;

     irq_desc[irq].mask =s3c2410_mask_irq;

     irq_desc[irq].unmask =s3c2410_unmask_irq;

    }

 

     irq_desc[IRQ_RESERVED6].valid =0;

     irq_desc[IRQ_RESERVED24].valid =0;

 

     irq_desc[IRQ_EINT4_7].valid =0;

     irq_desc[IRQ_EINT8_23].valid =0;

 

     irq_desc[IRQ_EINT0].valid  =0;

     irq_desc[IRQ_EINT1].valid  =0;

     irq_desc[IRQ_EINT2].valid  =0;

     irq_desc[IRQ_EINT3].valid  =0;

 

   for (irq=NORMAL_IRQ_OFFSET;irq <EXT_IRQ_OFFSET;irq++) {

 

 

     irq_desc[irq].valid =0;

     irq_desc[irq].probe_ok =1;

     irq_desc[irq].mask_ack =EINT4_23mask_ack_irq;

     irq_desc[irq].mask =EINT4_23mask_irq;

     irq_desc[irq].unmask =EINT4_23unmask_irq;

    }

   for (irq=EXT_IRQ_OFFSET;irq <SUB_IRQ_OFFSET;irq++) {

     irq_desc[irq].valid =1;

     irq_desc[irq].probe_ok =1;

     irq_desc[irq].mask_ack =SUB_mask_ack_irq;

     irq_desc[irq].mask =SUB_mask_irq;

     irq_desc[irq].unmask =SUB_unmask_irq;

    }  

}

       可以看出,初始化中断描述符表时,对EINT0、EINT1、EINT2、EINT3、EINT4_7、EINT8_23等的.valid元素复位为0,表示这几个中断不可用。结合request_irq()源码可以看出,在注册时就会出现这几个中断不能成功注册的现象,本来这几个值是1的,我真不知道开发者为何这样做!不过还得感谢他们,是他们做的这个手脚促使我学得了这么多原来不知道的东东。

       现在将这促人学习的几句注释掉,重新编译这个LINUX,重新启动这个新的内核,重新运行你的测试程序,一切都重新来,你将会兴奋的说:OK!

<think>嗯,用户问的是Linux中断处理中request_threaded_irqrequest_irq之间的区别。首先,我需要回忆这两个函数的基本用法和它们在内核中的作用。根据之前的引用,request_irq其实调用了request_threaded_irq,只是把thread_fn参数设为NULL。这说明request_threaded_irq是更底层的函数,而request_irq是其简化版本。 用户可能是在开发驱动时遇到了中断处理的问题,或者想优化现有的中断处理代码。他们可能想知道在什么情况下应该使用哪个函数,以及各自的优缺点。比如,request_threaded_irq支持线程化中断处理,这对于需要长时间处理的任务或者需要睡眠的情况很有用,而传统的request_irq可能在处理耗时操作时导致系统延迟。 根据引用[1],内核现在支持线程化中断,建议在适用的情况下优先使用request_threaded_irq,而不是用request_irq配合tasklet或工作队列。这可能涉及到中断处理的效率和系统响应速度的平衡。另外,引用[2]提到,当同时指定handler和thread_fn时,处理thread_fn时中断是开启的,这可能影响到中断嵌套和并发处理,需要特别注意。 用户可能还有疑问,比如错误处理,比如引用[4]中提到的devm_request_threaded_irq返回-22,这可能是因为参数错误,比如flags设置不正确。这时候需要检查flags是否符合要求,比如IRQF_ONESHOT在电平触发中断中的必要性。 还需要解释两者的核心区别,比如执行上下文:request_irq的处理函数在中断上下文中运行,不能阻塞,而request_threaded_irq的线程处理部分在进程上下文中,可以睡眠。这对驱动程序的设计有重要影响,尤其是需要访问可能阻塞的资源时。 另外,flags参数的不同,比如IRQF_ONESHOT的作用,以及线程化中断如何影响中断的嵌套和屏蔽状态。可能还要提到性能方面的考虑,线程化中断可能带来更高的灵活性,但也会增加上下文切换的开销,因此需要根据具体情况选择。 用户可能还想知道如何选择使用哪个函数,比如简单的中断处理用request_irq,复杂的需要阻塞操作用的用request_threaded_irq。同时,错误示例中的问题也需要解释清楚,帮助用户避免类似的错误。 最后,确保回答结构清晰,分点说明区别、使用场景、注意事项,并给出示例代码和常见问题解决方案,这样用户能够逐步理解并应用到实际开发中。</think>### `request_threaded_irq` vs `request_irq` 核心区别 以下是两者在Linux中断处理中的主要差异及使用场景分析: --- #### **1. 设计目标与执行上下文** | 特性 | `request_irq` | `request_threaded_irq` | |---------------------|----------------------------------|-----------------------------------------| | **执行上下文** | 硬中断上下文(不可阻塞) | 硬中断上下文(handler) + 线程上下文(thread_fn,可阻塞) | | **阻塞能力** | ❌ 禁止睡眠、耗时操作 | ✅ `thread_fn` 允许睡眠和调度 | | **适用场景** | 快速响应的简单中断(如GPIO触发) | 复杂处理或需要阻塞的操作(如I2C通信、等待资源) | --- #### **2. 参数差异与调用方式** - **`request_irq`** 是 `request_threaded_irq` 的简化封装,内部调用如下: ```c static inline int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) { return request_threaded_irq(irq, handler, NULL, flags, name, dev); } ``` 即 `thread_fn` 参数被设为 `NULL`[^3]。 - **`request_threaded_irq`** 提供更灵活的配置: ```c int request_threaded_irq(unsigned int irq, irq_handler_t handler, // 硬中断处理函数(可选) irq_handler_t thread_fn, // 线程化处理函数(必选其一) unsigned long flags, const char *name, void *dev); ``` - 若 `handler` 和 `thread_fn` 均非空: 硬中断处理完成后,触发线程化处理(此时中断处于**开启状态**)[^2]。 - 若 `handler` 为空且 `thread_fn` 非空: 直接在线程上下文中处理中断(中断处于**关闭状态**)。 --- #### **3. 中断处理流程对比** - **`request_irq` 流程** ```mermaid graph LR A[中断触发] --> B[硬中断处理函数] B --> C[禁止抢占] C --> D[快速处理逻辑] D --> E[恢复抢占] ``` - **`request_threaded_irq` 流程** ```mermaid graph LR A[中断触发] --> B{handler是否为空?} B -->|是| C[直接调度thread_fn] B -->|否| D[执行handler] D --> E[返回IRQ_WAKE_THREAD] E --> F[调度thread_fn] F --> G[线程处理逻辑] ``` --- #### **4. Flags标志位关键差异** - **`IRQF_ONESHOT`** 专用于线程化中断,确保中断线在 `thread_fn` 完成前保持禁用(常用于电平触发中断避免重复触发)[^4]。 - **`IRQF_SHARED`** 两者均可共享中断,但线程化中断需注意资源竞争问题。 --- #### **5. 性能与延迟权衡** | **维度** | `request_irq` | `request_threaded_irq` | |----------------|------------------------|-----------------------------| | **延迟** | 低(直接处理) | 较高(需调度线程) | | **吞吐量** | 适合高频小任务 | 适合低频复杂任务 | | **系统影响** | 可能阻塞其他中断 | 减少中断上下文占用 | --- ### **典型使用场景** 1. **`request_irq` 适用场景** - 按键中断(需快速响应) - 定时器中断(无阻塞操作) ```c request_irq(irq, simple_handler, IRQF_TRIGGER_RISING, "btn_irq", dev); ``` 2. **`request_threaded_irq` 适用场景** - 触摸屏驱动(需等待数据稳定) - 网络设备(需协议栈处理) ```c request_threaded_irq(irq, NULL, touch_thread_fn, IRQF_ONESHOT, "ts_irq", dev); ``` --- ### **常见问题与解决方案** **问题1:`devm_request_threaded_irq` 返回 `-EINVAL` (错误码-22)** - **原因**:参数不合法,常见于 `flags` 未正确设置 `IRQF_ONESHOT`(电平触发中断必须设置此标志)[^4]。 - **修复**: ```c flags |= IRQF_ONESHOT; // 电平触发中断必须添加 devm_request_threaded_irq(..., flags, ...); ``` **问题2:线程化中断处理延迟过高** - **优化方法**: - 减少 `thread_fn` 中的阻塞操作 - 使用 `IRQF_PER_CPU` 绑定到特定CPU核心 --- ### **代码示例** ```c // 线程化中断注册(混合模式) irqreturn_t hard_handler(int irq, void *dev_id) { // 快速读取状态寄存器 return IRQ_WAKE_THREAD; // 触发线程处理 } irqreturn_t thread_fn(int irq, void *dev_id) { // 执行耗时操作(如I2C传输) return IRQ_HANDLED; } // 注册函数 ret = request_threaded_irq(irq_num, hard_handler, thread_fn, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "mixed_irq", dev); ``` --- ### **总结选择建议** - **优先 `request_irq`**:中断处理逻辑简单且无需阻塞(如GPIO状态读取)。 - **优先 `request_threaded_irq`**:需要睡眠、复杂数据处理或与用户空间交互的场景。 - **混合模式**:硬中断处理关键操作,线程处理非实时任务(如日志记录)。 [^1]: 内核线程化中断支持背景 [^2]: 线程化中断执行状态差异 [^3]: 函数封装关系 : 参数错误案例分析
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值