linux+内核中断+vmap,ARM Linux对中断的处理

中断处理

OK,接下来,终于可以来研究中断处理了,也就是,我们辛辛苦苦添加进系统的中断处理例程被调用的整个过程。

不过,在分析源代码之前,还是让我们先了解一些原理性的基础知识吧。我们都知道在处理中断时要保存现场,也就是要保存产生中断时,各个寄存器的内容。然后才能开始处理中断,处理完之后还要把现场状态恢复过来,才能返回到被中断的地方继续执行。这里要说明的是在指令跳转到中断向量的地方开始执行之前,由CPU自动帮我们完成的事情:

R14_irq

=要执行的下一条指令+ 4,这里的下条指令是相对于被中断指令的下条,即返回地址,R14是中断模式下的R14。

SPSR_irq

= CPSR,保存的当前状态寄存器,r0到r12要由我们软件来保存(如果需要的话)。

CPSR[4:0]

= 0b10010,进入中断模式

CPSR[5]

= 0,在ARM模式下执行(不是Thumb下)

CPSR[7]

= 1,关掉IRQ中断,FIQ还是开着

PC

= 0Xffff0018,根据异常向量表的位置,跳转到特定的中断向量处去执行。

我们还是要回到异常向量表去看一下。每当中断控制器产生一个中断请求,则CPU总是会从异常向量表的中断向量处取指令来执行,在前面对异常向量表建立过程的研究中,我们看到,中断向量是一条跳转指令,跳转到vector_irq符号处,也就是中断处理入口,中断处理的入口由一个宏来建立,将该宏解开,则得到下面这样的一段代码(arch/arm/kernel/entry-armv.S中):

.align 5

vector_irq:

//修正返回地址,也就是中断处理完之后要执行的指令的地址

sub   lr, lr, #4

//保存返回地址,因为很快要使用r0寄存器,所以也要保存r0。

stmia  sp, {r0, lr}@ save r0, lr

mrs   lr, spsr

str   lr, [sp, #8]@

save spsr

//这个地方用的是向上增长的栈,而且没有改变栈顶指针。

//此时的这个栈是中断模式下的栈,ARM下中断模式下和系统模式下的

//栈是不同的。虽然ARM提供了七个模式,但Linux只使用了两个,一

//个是用户模式,另一个为系统模式,所以这个栈只是一个临时性的栈。

//把spsr设置为管理模式

mrs   r0, cpsr

eor   r0, r0, #(IRQ_MODE ^ SVC_MODE |

PSR_ISETSTATE)

msr   spsr_cxsf, r0

//跳转表必须紧跟在代码后面

and   lr, lr, #0x0f//这条指令之后lr中为spsr的低4位

//将临时栈的栈顶指针保存在r0寄存器中,传递给后面的中断处理过程

mov   r0, sp

ldr   lr, [pc, lr, lsl #2]

// PC寄存器中保存的是当前正在取指的地址,也就是当前正在执行的指令

//之后的第二条指令的地址,而不是紧接着当前指令的第一条指令的地址。

//以spsr的低4位为索引,以PC值为基地址来获得相应的中断处理

//程序的地址

movs  pc, lr@ branch to handler in SVC mode

// movs的目的对象如果是pc的话,则还会把spsr赋值给cpsr,上面我

//们看到spsr被设成管理模式,因此这条语句过后的代码也就跑在了管

//理模式下。

//可以看到该汇编代码主要是把被中断的代码在执行过程中的状态(cpsr), //返回地址(lr)等保存在中断模式下的栈里,然后进 入到管理模式下去执//行中断,同时令r0 = sp,这样可以在管理模式下找到该地址,进而获取// spsr等信息。该汇编代码最终根据被中断的代码所处的模式跳转到相应//的处理程序中去。

//注意,管理模式下的栈和中断模式下的栈不是同一个。同时由于在上面

//的代码中栈指针(sp)没有改变,因此即使后面的代码没对这个栈进行

//出栈操作,也不会因为不断的产生中断而导致栈溢出。

.long __irq_usr       @  0

(USR_26 / USR_32)

.long __irq_invalid         @  1

(FIQ_26 / FIQ_32)

.long __irq_invalid         @  2

(IRQ_26 / IRQ_32)

.long __irq_svc       @  3

(SVC_26 / SVC_32)

.long __irq_invalid         @  4

.long __irq_invalid         @  5

.long __irq_invalid         @  6

.long __irq_invalid         @  7

.long __irq_invalid         @  8

.long __irq_invalid         @  9

.long __irq_invalid         @  a

.long __irq_invalid         @  b

.long __irq_invalid         @  c

.long __irq_invalid         @  d

.long __irq_invalid         @  e

.long __irq_invalid         @  f

前面有提到过,这是一段很巧妙的位置无关的代码,它将中断产生时,CPSR的模式位的值作为相对于PC值的索引来调用相应的中断处理程序。如果在进入终中断时是用户模式,则调用__irq_usr例程,如果为系统模式,则调用__irq_svc,如果是其他模式,则说明出错了,则调用__irq_invalid。接下来我们分别瞧一下这些个不同模式的中断处理程序。

内核模式下的中断处理

内核模式下的中断处理,也就是调用__irq_svc例程,__irq_svc例程在文件arch/arm/kernel/entry-armv.S中定义,首先我们来看这个例程的定义:

__irq_svc:

svc_entry

#ifdef CONFIG_PREEMPT

get_thread_info

tsk

ldr   r8, [tsk, #TI_PREEMPT]      @ get preempt count

add   r7, r8, #1         @

increment it

str   r7, [tsk, #TI_PREEMPT]

#endif

irq_handler

#ifdef CONFIG_PREEMPT

str   r8, [tsk, #TI_PREEMPT]      @ restore preempt count

ldr   r0, [tsk, #TI_FLAGS]     @ get flags

teq   r8, #0          @

if preempt count != 0

movne r0, #0          @

force flags to 0

tst   r0, #_TIF_NEED_RESCHED

blne  svc_preempt

#endif

ldr   r4, [sp, #S_PSR]      @ irqs are already disabled

#ifdef CONFIG_TRACE_IRQFLAGS

tst   r4, #PSR_I_BIT

bleq  trace_hardirqs_on

#endif

svc_exit

r4           @ return from exception

UNWIND(.fnend     )

ENDPROC(__irq_svc)

首先来看上面的svc_entry,这是一个宏,也在arch/arm/kernel/entry-armv.S中定义:

.macro svc_entry, stack_hole=0

UNWIND(.fnstart      )

UNWIND(.save {r0 - pc}     )

//在栈中分配一个栈帧的空间用来存储各个寄存器的值。

// S_FRAME_SIZE在arch/arm/kernel/asm-offsets.c中定义,值为:

//  DEFINE(S_FRAME_SIZE,    sizeof(struct pt_regs));实际上

//等于72。最后之所以又加了个4,是因为下面保存寄存器是从r1开始的

//满递减的栈

sub   sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)

//检查栈指针的对齐,内核要求此时的栈指针是8字节对齐的

#ifdef CONFIG_THUMB2_KERNEL

SPFIX(  str   r0, [sp] )  @ temporarily saved

SPFIX(  mov   r0, sp   )

SPFIX(  tst   r0, #4   )  @ test original stack alignment

SPFIX(  ldr   r0, [sp] )  @ restored

#else

SPFIX(  tst   sp, #4   )

#endif

SPFIX(  subeq sp, sp, #4  )

//

sp指向struct

pt_regs结构底部,简单的多寄存器存储指令

stmia sp, {r1 - r12}//保存r1到r12的值

//在前面我们看到r0中存储的是进入中断时的临时栈的栈指针,在这个地址

//处存储有r0,lr和spsr,将这三个值分别加载进r1-r3寄存器中

ldmia r0, {r1 - r3}

// S_SP为sp寄存器在pt_regs中的偏移,在文

//件arch/arm/kernel/asm-offsets.c中定义,值为:

// DEFINE(S_SP,          offsetof(struct pt_regs, ARM_sp));

// struct

pt_regs {

// long uregs[18];

// };

//则寄存器r5中存放的是pt_regs结构中存储SP的位置

add   r5, sp, #S_SP - 4  @ here for interlock avoidance

mov   r4, #-1         @  ""

""

""       ""

// r0为中断发生以前的堆栈指针,将成为pt_regs中的sp的值

add   r0, sp, #(S_FRAME_SIZE + \stack_hole - 4)

SPFIX(  addeq r0, r0, #4  )

//保存实际的r0,并使得sp指向栈帧的开始地址。

str   r1, [sp, #-4]!     @ save the "real" r0 copied

@

from the exception stack

mov   r1, lr

@

@

We are now ready to fill in the remaining blanks on the stack:

@

@  r0 - sp_svc

@  r1 - lr_svc

@  r2 - lr_, already fixed up

for correct return/restart

@  r3 - spsr_

@  r4 - orig_r0 (see pt_regs definition in

ptrace.h)

@

stmia r5, {r0 - r4}

//这一段代码保存所有的寄存器

asm_trace_hardirqs_off

.endm

这个宏主要就是保存各个寄存器值到栈上相应的位置,这个宏执 行完后的栈如下所示:

clip_image002.jpg

接着来看get_thread_info,它也是个宏,用来获取当前线程的地址。如果配置了内核抢占,则会执行宏展开的代码。线程的定义在include/linux/sched.h中:

union

thread_union {

struct thread_info thread_info; //线程属性

unsigned long

stack[THREAD_SIZE/sizeof(long)]; //栈

};

由它定义的线程是8K字节边界对齐的,并且在这8K的最低地址处存放的就是thread_info对象,即该栈拥有者线程的对象,而get_thread_info就是通过把sp低13位清0(8K边 界)来获取当前thread_info对象的地址。get_thread_info宏在arch/arm/kernel/entry-header.S中定义:

.macro get_thread_info, rd

mov   \rd, sp, lsr #13

mov   \rd, \rd, lsl #13

.endm

调用该宏后寄存器tsk里存放的就是当前线程对象的地址了,tsk是哪个寄存器呢,在arch/arm/kernel/entry-header.S文件中我们看到:

tsk

.req    r9      @ current thread_info

tsk只是r9的别名而已, 因此这时r9里保存的就是当前线程的地址。上面的那一段代码主要完成的工作即是获得线程对象基地址,进而增加线程对象的抢占计数

接着看irq_handler,它在文件arch/arm/kernel/entry-armv.S中定义:

.macro irq_handler

get_irqnr_preamble

r5, lr

1: get_irqnr_and_base

r0, r6, r5, lr//平台相关,获取中断号

movne r1, sp

@

@

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

@

//中断处理完成后返回的地方:获得中断号的地方,根据中断控制器中相

//应寄存器的内容作为退出条件。退出时下面的两行代码就会被略过去。

adrne lr, BSYM(1b)

//通过上面的宏get_irqnr_and_base为调用asm_do_IRQ准备了参数中断号

// struct

pt_regs *参数也早已获得,于是乎调用asm_do_IRQ来处理中断

bne   asm_do_IRQ

#ifdef CONFIG_SMP

/*

* XXX

*

* this macro assumes that irqstat (r6) and

base (r5) are

* preserved from get_irqnr_and_base above

*/

test_for_ipi

r0, r6, r5, lr

movne r0, sp

adrne lr, BSYM(1b)

bne   do_IPI

#ifdef CONFIG_LOCAL_TIMERS

test_for_ltirq

r0, r6, r5, lr

movne r0, sp

adrne lr, BSYM(1b)

bne   do_local_timer

#endif

#endif

.endm

对于我们的平台来说get_irqnr_preamble是空的宏。irq_handler首先通过宏get_irqnr_and_base获得中断号,存入r0。然后把上面建立的pt_regs结构的指针,也就是sp值赋给r1,把调用宏get_irqnr_and_base的位置作为返回地址(为了循环地处理挂起的所有中断)。最后调用asm_do_IRQ进一步处理中断。以上这些操作都建立在获得中断号的前提下,也就是有中断发生,某个外部设备触发中断的时候,kernel最终会调用到asm_do_IRQ()函数。

get_irqnr_and_base是平台相关的,这个宏查询ISPR(IRQ挂起中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中。该宏结束后,r0 =中断号。这个宏在不同的ARM芯片上是不一样的,它需要读写中断控制器中的寄存器。对于s3c2440,代码在arch/arm/mach-s3c2410/include/entry-macro.S里,用上面的调用参数将宏展开,如下:

1:

mov   r5, #S3C24XX_VA_IRQ

@@

try the interrupt offset register, since it is there

ldr   r6, [ r5, #INTPND ]

teq   r6, #0

beq   1002f

ldr   r0, [ r5, #INTOFFSET ]

mov   lr, #1

tst   r6, lr, lsl r0

bne   1001f

@@

the number specified is not a valid irq, so try

@@

and work it out for ourselves

mov   r0, #0   @@

start here

@@

work out which irq (if any) we got

movs  lr, r6, lsl#16

addeq r0, r0, #16

moveq r6, r6, lsr#16

tst   r6, #0xff

addeq r0, r0, #8

moveq r6, r6, lsr#8

tst   r6, #0xf

addeq r0, r0, #4

moveq r6, r6, lsr#4

tst   r6, #0x3

addeq r0, r0, #2

moveq r6, r6, lsr#2

tst   r6, #0x1

addeq r0, r0, #1

@@

we have the value

1001:

adds  r0, r0, #IRQ_EINT0

1002:

@@

exit here, Z flag unset if IRQ

我们把__irq_svc的汇编部分分析完后再来分析asm_do_IRQ()等c函数。宏irq_handler执行完毕,如果配置了抢占,则还会恢复线程对象的抢占计数,获得线程对象的标记字段值,以检查是否需要重新调度。

__irq_svc例程调用svc_exit宏来退出中断处理过程。前面的一条语句,我们看到,中断发生时的CPSR被保存在了r4寄存器中了,这个宏在arch/arm/kernel/entry-armv.S中定义:

.macro svc_exit, rpsr

msr   spsr_cxsf, \rpsr

#if defined(CONFIG_CPU_32v6K)

clrex              @ clear the exclusive monitor

ldmia sp, {r0 - pc}^        @

load r0 - pc, cpsr

#elif defined (CONFIG_CPU_V6)

ldr   r0, [sp]

strex r1, r2, [sp]       @

clear the exclusive monitor

ldmib sp, {r1 - pc}^        @

load r1 - pc, cpsr

#else

ldmia sp, {r0 - pc}^        @

load r0 - pc, cpsr

#endif

.endm

这个宏恢复中断时运行环境,也就是各个寄存器中的值,从而推出中断的处理过程。

OK,中断的流程大体就是这样的,下面我们就开始分析c部分的中断处理流程。在上面的汇编语言代码里,我们看到,系统在保存好中断时环境,获得中断号之后,调用了函数asm_do_IRQ(),从而进入中断处理的C程序部分。asm_do_IRQ()函数定义如下:

---------------------------------------------------------------------

arch/arm/kernel/irq.c

105 asmlinkage void __exception

asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

106 {

107         struct pt_regs *old_regs =

set_irq_regs(regs);

108

109         irq_enter();

110

111         /*

112          * Some hardware gives randomly wrong

interrupts.  Rather

113          * than crashing, do something

sensible.

114          */

115         if (unlikely(irq >= NR_IRQS)) {

116                 if (printk_ratelimit())

117                         printk(KERN_WARNING

"Bad IRQ%u\n", irq);

118                 ack_bad_irq(irq);

119         } else {

120                 generic_handle_irq(irq);

121         }

122

123         /* AT91 specific workaround */

124         irq_finish(irq);

125

126         irq_exit();

127         set_irq_regs(old_regs);

128 }

---------------------------------------------------------------------

这个函数完成如下操作:

1、调用set_irq_regs(regs)函数更新处理器的当前帧指针,并在局部变量old_regs中保存老的帧指针。

---------------------------------------------------------------------

include/asm-generic/irq_regs.h

21 DECLARE_PER_CPU(struct pt_regs *, __irq_regs);

28 static inline struct pt_regs

*set_irq_regs(struct pt_regs *new_regs)

29 {

30   struct pt_regs *old_regs, **pp_regs =

&__get_cpu_var(__irq_regs);

31

32

old_regs = *pp_regs;

33

*pp_regs = new_regs;

34

return old_regs;

35 }

---------------------------------------------------------------------

2、调用irq_enter()进入一个中断处理上下文。

3、检查中断号的有效性,有些硬件会随机的给一些错误的中断,做一些检查以防止系统崩溃。如果不正确,就调用ack_bad_irq(irq),该函数会增加用来表征发生的错误中断数量的变量irq_err_count,这个变量貌似仅供了解系统状况之用。

4、若传递的中断号有效,则会掉用generic_handle_irq(irq)来处理中断。

5、调用irq_exit()来推出中断处理上下文。

6、调用set_irq_regs(old_regs)来恢复处理器的当前帧指针。

接下来我们来看看函数generic_handle_irq()对于中断的处理,这个函数仅仅是对generic_handle_irq_desc()函数的封装而已:

---------------------------------------------------------------------

include/linux/irq.h

320 static inline void

generic_handle_irq(unsigned int irq)

321 {

322         generic_handle_irq_desc(irq,

irq_to_desc(irq));

323 }

---------------------------------------------------------------------

generic_handle_irq_desc()函数才是最值得我们关注的:

---------------------------------------------------------------------

include/linux/irq.h

308 static inline void

generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)

309 {

310 #ifdef

CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ

311         desc->handle_irq(irq, desc);

312 #else

313         if (likely(desc->handle_irq))

314                 desc->handle_irq(irq,

desc);

315         else

316                 __do_IRQ(irq);

317 #endif

318 }

---------------------------------------------------------------------

这个函数接收两个参数,中断号及对应的中断描述符指针。体系架构相关的中断处理函数调用这个函数来进行通用IRQ层的中断处理。如果中断的irq_desc结构的handle_irq成员非空则调用它。否则,会调用__do_IRQ()来让通用的IRQ层来处理一个中断。中断描述符irq_desc结构的handle_irq成员因中断类型的不同而不同,在我们前面分析的芯片级中断初始化函数s3c24xx_init_irq()中,我们看到这个字段基本上被设置为了这么几个函数:

用来处理具有多个子中断源的中断线的情况(SoC中断控制器的特性,而不是中断共享)的一组函数s3c_irq_demux_extint4t7()、s3c_irq_demux_extint8()、s3c_irq_demux_uart0()、s3c_irq_demux_uart1)、s3c_irq_demux_uart2()、s3c_irq_demux_adc()

其他情况的handle_edge_irq()函数

还有handle_level_irq()函数,只是很快就被第一种情况的几个函数取代。

OMG,这个地方似乎好复杂,如此之多的函数。不过,结构还是蛮清晰的。首先我们来看这几个特定于SoC的函数:

---------------------------------------------------------------------

arch/arm/plat-s3c24xx/irq.c

370 static void s3c_irq_demux_adc(unsigned int irq,

371                               struct irq_desc

*desc)

372 {

373

unsigned int subsrc, submsk;

374

unsigned int offset = 9;

375

376

/* read the current pending interrupts, and the mask

377

* for what it is available */

378

379

subsrc = __raw_readl(S3C2410_SUBSRCPND);

380

submsk = __raw_readl(S3C2410_INTSUBMSK);

381

382

subsrc &= ~submsk;

383

subsrc >>= offset;

384

subsrc &= 3;

385

386

if (subsrc != 0) {

387       if (subsrc & 1) {

388             generic_handle_irq(IRQ_TC);

389       }

390       if (subsrc & 2) {

391             generic_handle_irq(IRQ_ADC);

392

}

393

}

394 }

396 static void s3c_irq_demux_uart(unsigned int start)

397 {

398

unsigned int subsrc, submsk;

399

unsigned int offset = start - IRQ_S3CUART_RX0;

400

401

/* read the current pending interrupts, and the mask

402

* for what it is available */

403

404

subsrc = __raw_readl(S3C2410_SUBSRCPND);

405

submsk = __raw_readl(S3C2410_INTSUBMSK);

406

407 irqdbf2("s3c_irq_demux_uart: start=%d (%d),

subsrc=0x%08x,0x%08x\n",

408           start, offset, subsrc, submsk);

409

410

subsrc &= ~submsk;

411

subsrc >>= offset;

412

subsrc &= 7;

413

414

if (subsrc != 0) {

415       if (subsrc & 1)

416             generic_handle_irq(start);

417

418       if (subsrc & 2)

419             generic_handle_irq(start+1);

420

421       if (subsrc & 4)

422             generic_handle_irq(start+2);

423

}

424 }

428 static void

429 s3c_irq_demux_uart0(unsigned int irq,

430                     struct irq_desc *desc)

431 {

432

irq = irq;

433

s3c_irq_demux_uart(IRQ_S3CUART_RX0);

434 }

435

436 static void

437 s3c_irq_demux_uart1(unsigned int irq,

438                     struct irq_desc *desc)

439 {

440

irq = irq;

441

s3c_irq_demux_uart(IRQ_S3CUART_RX1);

442 }

443

444 static void

445 s3c_irq_demux_uart2(unsigned int irq,

446                     struct irq_desc *desc)

447 {

448

irq = irq;

449

s3c_irq_demux_uart(IRQ_S3CUART_RX2);

450 }

452 static void

453 s3c_irq_demux_extint8(unsigned int irq,

454                       struct irq_desc *desc)

455 {

456

unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);

457

unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);

458

459

eintpnd &= ~eintmsk;

460

eintpnd &= ~0xff;       /*

ignore lower irqs */

461

462

/* we may as well handle all the pending IRQs here */

463

464

while (eintpnd) {

465       irq = __ffs(eintpnd);

466       eintpnd &= ~(1<

467

468       irq += (IRQ_EINT4 - 4);

469       generic_handle_irq(irq);

470

}

471

472 }

474 static void

475 s3c_irq_demux_extint4t7(unsigned int irq,

476                         struct irq_desc *desc)

477 {

478

unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);

479

unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);

480

481

eintpnd &= ~eintmsk;

482

eintpnd &= 0xff;        /*

only lower irqs */

483

484

/* we may as well handle all the pending IRQs here */

485

486

while (eintpnd) {

487       irq = __ffs(eintpnd);

488       eintpnd &= ~(1<

489

490       irq += (IRQ_EINT4 - 4);

491

492       generic_handle_irq(irq);

493

}

494 }

---------------------------------------------------------------------

话说这几个函数执行的操作都还是非常相似的:

SoC中断控制器中有一个中断挂起寄存器SRCPND,当相应的中断发生时,这个寄存器中相应的位就被置位,寄存器总共有32位。而实际上SoC支持多得多的中断源,于是中断控制器被扩展,中断挂起寄存器SRCPND中有些位可以表征多个中断源的发生,然后另外有子中断源挂起寄存器SUBSRCPND等来告诉系统到底发生的中断是哪一个。上面的这组函数就是找到产生中断的子中断源的中断号,然后用找到的这个中断号做为参数来调用generic_handle_irq(irq)。更多详细情况可以参考S3C24XX系列SoC的数据手册中断控制器的相关部分内容。

在前面的中断系统初始化函数中我们看到,如果中断线没有子中断源的话,则其中断描述符的handle_irq字段会被设置为handle_edge_irq()函数,接下来我们来看handle_edge_irq()函数:

---------------------------------------------------------------------

krnel/irq/chip.c

578 void

579 handle_edge_irq(unsigned int irq,

struct irq_desc *desc)

580 {

581

raw_spin_lock(&desc->lock);

582

583

desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

584

585

/*

586

* If we're currently running this IRQ, or its disabled,

587

* we shouldn't process the IRQ. Mark it pending, handle

588

* the necessary masking and go out

589

*/

590

if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||

591               !desc->action)) {

592        desc->status |= (IRQ_PENDING |

IRQ_MASKED);

593        mask_ack_irq(desc, irq);

594        goto out_unlock;

595

}

596

kstat_incr_irqs_this_cpu(irq, desc);

597

598

/* Start handling the irq */

599

if (desc->chip->ack)

600        desc->chip->ack(irq);

601

602

/* Mark the IRQ currently in progress.*/

603

desc->status |= IRQ_INPROGRESS;

604

605

do {

606        struct

irqaction *action = desc->action;

607        irqreturn_t action_ret;

608

609        if (unlikely(!action)) {

610             mask_irq(desc, irq);

611             goto out_unlock;

612        }

613

614        /*

615         * When another irq arrived while we

were handling

616         * one, we could have masked the irq.

617         * Renable it, if it was not disabled

in meantime.

618         */

619        if (unlikely((desc->status &

620                    (IRQ_PENDING | IRQ_MASKED |

IRQ_DISABLED)) ==

621                    (IRQ_PENDING |

IRQ_MASKED))) {

622               unmask_irq(desc, irq);

623        }

624

625        desc->status &= ~IRQ_PENDING;

626        raw_spin_unlock(&desc->lock);

627        action_ret = handle_IRQ_event(irq,

action);

628        if (!noirqdebug)

629              note_interrupt(irq, desc,

action_ret);

630        raw_spin_lock(&desc->lock);

631

632

} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) ==

IRQ_PENDING);

633

634

desc->status &=

~IRQ_INPROGRESS;

635 out_unlock:

636

raw_spin_unlock(&desc->lock);

637 }

---------------------------------------------------------------------

这个函数接收两个参数,irq为中断号,desc为相应的中断描述符。中断发生在硬件信号的上升沿或下降沿。中断被锁进中断控制器,中断必须被确认,以重新使能。在中断被确认之后,则另一个在相同的中断源上的中断就可能会发生,即使前面的一个正在被相关的中断处理程序处理。如果这种情况发生了,则通过硬件控制器,禁用中断是必要的。这需要在处理中断处理程序执行时产生的中断的中断处理循环中重新使能中断。如果所有挂起的中断都已经被处理,则循环退出。

这个函数完成如下操作:

1、获得中断描述符的自旋锁。

2、清除中断描述符状态字段status的IRQ_REPLAY和IRQ_WAITING标志。

3、检查desc->status及desc->action,若desc->status设置了IRQ_INPROGRESS或IRQ_DISABLED标志,即中断处理中或中断禁用,或者desc->action为空,则设置desc->status的IRQ_PENDING和IRQ_MASKED标志,屏蔽并确认中断,释放自旋锁并退出。

4、增加中断产生计数值。

5、若desc->chip->ack非空,则调用desc->chip->ack(irq)开始处理中断。

6、标记IRQ处理当前正在进行中。

7、通过一个循环来处理中断。主要完成的工作即是调用中断描述符的irqaction链。

在这里我们可以看一下Linux内核中对于中断嵌套的处理。Linux使用desc->status的IRQ_INPROGRESS来标记中断处理正在进行中,当第一次进入中断处理时,设置相应的中断描述符状态字段的该标志。则在重新使能中断后,即使前面的中断处理过程还没有结束,依然有可能会产生中断会进入中断处理流程。则在后面的中断处理流程里,进入handle_edge_irq()后,检测到前一个中断处理流程没有结束,则仅仅是设置desc->status的IRQ_PENDING和IRQ_MASKED标志便迅速退出。而在前一个中断处理流程的handle_edge_irq()的一个do{}while循环结束后,会检查desc->status的IRQ_PENDING和IRQ_MASKED标志,若设置了这两个标志,则会进行另外的一个中断处理do{}while循环。

8、清除desc->status的IRQ_INPROGRESS标志,释放自旋锁。

我们接着来看handle_IRQ_event()函数,这个函数定义为:

---------------------------------------------------------------------

kernel/irq/handle.c

368 irqreturn_t

handle_IRQ_event(unsigned int irq, struct irqaction *action)

369 {

370

irqreturn_t ret, retval = IRQ_NONE;

371

unsigned int status = 0;

372

373   if (!(action->flags & IRQF_DISABLED))

374       local_irq_enable_in_hardirq();

375

376   do {

377       trace_irq_handler_entry(irq, action);

378       ret = action->handler(irq,

action->dev_id);

379       trace_irq_handler_exit(irq, action,

ret);

380

381       switch (ret) {

382

case IRQ_WAKE_THREAD:

383

/*

384

* Set result to handled so the spurious

check

385

* does not trigger.

386

*/

387

ret = IRQ_HANDLED;

388

389

/*

390            * Catch drivers which return

WAKE_THREAD but

391            * did not set up a thread function

392            */

393           if (unlikely(!action->thread_fn))

{

394                 warn_no_thread(irq, action);

395                 break;

396           }

397

398           /*

399            * Wake up the handler thread for

this

400            * action. In case the thread

crashed and was

401            * killed we just pretend that we

handled the

402            * interrupt. The hardirq handler above

has

403            * disabled the device interrupt, so

no irq

404            * storm is lurking.

405            */

406            if (likely(!test_bit(IRQTF_DIED,

407                 &action->thread_flags)))

{

408                 set_bit(IRQTF_RUNTHREAD,

&action->thread_flags);

409                 wake_up_process(action->thread);

410            }

411

412            /* Fall through to add to

randomness */

413             case IRQ_HANDLED:

414                  status |= action->flags;

415                  break;

416

417             default:

418                  break;

419        }

420

421        retval |= ret;

422        action = action->next;

423

} while (action);

424

425

if (status & IRQF_SAMPLE_RANDOM)

426

add_interrupt_randomness(irq);

427

local_irq_disable();

428

429

return retval;

430 }

---------------------------------------------------------------------

该函数主要的工作即是逐个地调用特定中断号的action链表的handler函数,也就是我们在驱动程序中用request_irq注册的中断例程。这里需要注意的是:如果我们注册中断的时候指明可以共享的话,则必须在我们的中断例程里判断当前产生的中断是否就是我们自己的中断,这可以通过传进来的参数来判断(该参数就是我们注册时提供的action->dev_id)。

接下来来看handle_level_irq()函数,其定义为:

---------------------------------------------------------------------

kernel/irq/handle.c

472 void

473 handle_level_irq(unsigned int irq,

struct irq_desc *desc)

474 {

475         struct irqaction *action;

476         irqreturn_t action_ret;

477

478         raw_spin_lock(&desc->lock);

479         mask_ack_irq(desc, irq);

480

481         if (unlikely(desc->status &

IRQ_INPROGRESS))

482                 goto out_unlock;

483         desc->status &= ~(IRQ_REPLAY |

IRQ_WAITING);

484         kstat_incr_irqs_this_cpu(irq, desc);

485

486         /*

487          * If its disabled or no action available

488          * keep it masked and get out of here

489          */

490         action = desc->action;

491         if (unlikely(!action ||

(desc->status & IRQ_DISABLED)))

492                 goto out_unlock;

493

494         desc->status |= IRQ_INPROGRESS;

495         raw_spin_unlock(&desc->lock);

496

497         action_ret = handle_IRQ_event(irq,

action);

498         if (!noirqdebug)

499                 note_interrupt(irq, desc,

action_ret);

500

501         raw_spin_lock(&desc->lock);

502         desc->status &=

~IRQ_INPROGRESS;

503

504         if (!(desc->status &

(IRQ_DISABLED | IRQ_ONESHOT)))

505                 unmask_irq(desc, irq);

506 out_unlock:

507         raw_spin_unlock(&desc->lock);

508 }

509 EXPORT_SYMBOL_GPL(handle_level_irq);

---------------------------------------------------------------------

这个函数的功能基本上和handle_edge_irq()相同,只不过这个函数用来处理电平触发的中断,而handle_edge_irq()则用来处理边缘触发的中断。

OK,到现在,则系统模式下中断的整个处理过程则大致分析完了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值