Chinaunix首页 | 论坛 | 博客
  • 博客访问: 58745
  • 博文数量: 8
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 155
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-07 15:22
文章分类
文章存档

2014年(3)

2013年(5)

我的朋友

分类: LINUX

2013-05-03 20:28:25

中断子系统是linux内核非常重要的一个子系统,中断是系统对设备进行管理最常见也可以说是最有效的一种方式,在各种设备驱动中我们都可以看到设备向系统申请注册中断的地方。在我们平常使用中断的时候,大多都是根据设备的中断号向系统申请注册(request_irq)一个中断及中断处理函数,然后实现好中断处理函数就差不多了。我们也都知道,当中断产生时,系统就会去执行中断处理函数,从而实现对设备的有效管理。那么,系统是如何实现这些的呢?

异常向量表的创建:

    首先我们来了解当中断产生时,系统是如何响应中断的。

    中断是作为异常的一种被CPU响应处理的。系统在启动过程中作建立一个异常向量表,当异常产生的时候,CPU就会通过异常向量表获得相应的异常处理地址,从而对相应的异常进行处理。在ARM架构中,异常向量的基地址有两个:0x000000000xffff0000。这个地址是可以通过设置而确定的。在我使用的系统当中它是0xffff0000。当异常产生时,CPU就会跳转到异常向量的基地址处,那么这个地方有什么呢?

    在系统启动过程中,会定义一个异常向量表,并将表拷贝到0xffff0000这个地址。在traps.c中有void __init early_trap_init(void *vectors_base) {

unsigned long vectors = (unsigned long)vectors_base;

extern char __stubs_start[], __stubs_end[];

extern char __vectors_start[], __vectors_end[];

extern char __kuser_helper_start[], __kuser_helper_end[];

int kuser_sz = __kuser_helper_end - __kuser_helper_start;

vectors_page = vectors_base;

/*

 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)

 * into the vector page, mapped at 0xffff0000, and ensure these

 * are visible to the instruction stream.

 */

memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

……..

};

    其中这个vectors_base就是异常向量基地址,它的值就是0xffff0000。其中最重要的两行代码就是:

memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

        memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

    它分别将__vectors_start__stubs_start的内容拷贝到vectorsvectors+200处,下面我们来看看__vectors_start__stubs_start,到底有什么。

entry-armv.s中有:

__vectors_start:

 ARM( swi SYS_ERROR0 )

 THUMB( svc #0 )

 THUMB( nop )

W(b) vector_und + stubs_offset

W(ldr) pc, .LCvswi + stubs_offset

W(b) vector_pabt + stubs_offset

W(b) vector_dabt + stubs_offset

W(b) vector_addrexcptn + stubs_offset

W(b) vector_irq + stubs_offset

W(b) vector_fiq + stubs_offset

.globl __vectors_end

__vectors_end:



.globl __stubs_start

__stubs_start:

/*

 * Interrupt dispatcher

 */

vector_stub irq, IRQ_MODE, 4

.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

…….

.globl __stubs_end

__stubs_end:

    __vectors_start:是定义的异常向量表,它被拷贝到0xffff0000处,当异常产生时,CPU跳转0xffff0000,也就是开始执行__vectors_start之后的代码。根据代码可知,不同的异常,它会跳转到不同的异常处理地址,如当中断产生时它会跳转到:

W(b) vector_irq + stubs_offset

    __stubs_start是处理跳转的部分。我们把vector_stub irq, IRQ_MODE, 4 宏展开有:

.macro vector_stub, name, mode, correction=0

.align 5

vector_\name:

.if \correction

sub lr, lr, #\correction

.endif

@

@ Save r0, lr_ (parent PC) and spsr_

@ (parent CPSR)

@

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

mrs lr, spsr

str lr, [sp, #8] @ save spsr

@

@ Prepare for SVC32 mode.  IRQs remain disabled.

@

mrs r0, cpsr

eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)

msr spsr_cxsf, r0

@

@ the branch table must immediately follow this code

@

and lr, lr, #0x0f

 THUMB( adr r0, 1f )

 THUMB( ldr lr, [r0, lr, lsl #2] )

mov r0, sp

 ARM( ldr lr, [pc, lr, lsl #2] )

movs pc, lr @ branch to handler in SVC mode

ENDPROC(vector_\name)

    因此我们可以看到,_stubs_start中定义的vector_stub irq, IRQ_MODE, 4就是前面异常向量表中要使用到的vector_irq。它保存了一些寄存器的值,并根据CPU的状态跳转到__irq_usr 或者__irq_svn

    这里又有一个问题,为什么它不直接跳转vector_irq而且是跳转到vector_irq + stubs_offset,我们来看一下stubs_offset的定义:

    .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

    前面有说了,__vectors_start被拷贝到0xffff0000处,而__stubs_start被拷贝到0xffff0000+0x200处,画个图来看一下:

    如图所示,当中断异常产生时,CPU执行到W(b) vector_irq + stubs_offset时它会从当前位置(pc)跳转到vertor_irq处来处理中断异常,那么从pcvertor_irq的距离offset是多少呢,就是0x200加上vertor_irq__stub_start距离再减去vectors_startpc的位置,也就是offset=0x200+vertor_irq-__stub_start-(pc-vertors_start)

=vertors_start+0x200-__stub_start+(vertor_irq-pc)

    CPU执行的当前地址是pc,跳转距离是offset,那么目标地址就是pc+offset了,那么目标地址也就是pc+(vertor_irq-pc)+vertors_start+0x200-__stub_start也就是vertor_irq+stubs_offset了。

跳转到vertor_irq之后根据CPU状态会去执行__irq_usr或者__irq_svc,分析__irq_svc:

__irq_svc:

svc_entry

irq_handler

……

这里重点分析irq_handler

.macro irq_handler

#ifdef CONFIG_MULTI_IRQ_HANDLER

ldr r1, =handle_arch_irq

mov r0, sp

adr lr, BSYM(9997f)

ldr pc, [r1]

#else

arch_irq_handler_default

#endif

9997:

.endm

从这里我们看到了,当执行到irq_handler的时候它就会去执行handler_arch_irq(默认是执行arch_irq_handler_default,但在我的系统当中它有定义CONFIG_MULTI_IRQ_HANDLER)。

到这里汇编部分执行完了,开始步入C代码当中。

执行中断处理函数:

根据上面异常向量表的分析可知,当中断产生后系统会去执行handler_arch_irq。那么在handler_arch_irq会做些什么呢?

系统启动里,会执行main.cstart_kernel()来对系统进行初始化,在start_kernelsetup_arch(&command_line)中有:

……

#ifdef CONFIG_MULTI_IRQ_HANDLER

handle_arch_irq = mdesc->handle_irq;

#endif

……

也就是handle_arch_irq保存的是mdesc->handle_irq

mdesc中保存是系统体系架构相关的一些初始化配置:

MACHIE_START(EMEDKB,“PXA988)

.map_io=mmp_map_io,

.init_early=pxa988_init_early,

.init_irq=pxa988_init_irq,

.timer=&pxa988_timer,

.reserve=pxa988_reserve,

.handle_irq=gic_handle_irq,

.init_machine=emeidkb_init_common,

.restart=mmp_arch_reset,

MACHINE_END

这个结构比较重要,每个型号的主板都会有对应的一个这样的结构。

从这里可以看到,handle_arch_irq保存的是machine_desc结构的handle_irq指针:

asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)

{

u32 irqstat, irqnr;

struct gic_chip_data *gic = &gic_data[0];

void __iomem *cpu_base = gic_data_cpu_base(gic);

do {

irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);

irqnr = irqstat & ~0x1c00;

if (likely(irqnr > 15 && irqnr < 1021)) {

irqnr = irq_find_mapping(gic->domain, irqnr);

handle_IRQ(irqnr, regs);

continue;

}

if (irqnr < 16) {

writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);

#ifdef CONFIG_SMP

handle_IPI(irqnr, regs);

#endif

continue;

}

break;

} while (1);

}

在这个函数里头,它首先从中断控制器中读取触发中断的硬件中断号,再通过一个映射表获取到软件中断号,然后进入handle_IRQ();再经过generic_handle_irq()->generic_handle_irq_desc():

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

{

desc->handle_irq(irq, desc);

}

开头有讲,我们在设备驱动当中会根据中断号申请注册中断和中断处理函数,而中断产生后,在这里系统会根据中断号来执行相应的中断处理函数,也就是desc->handle_irq(irq,desc);到这里,当一次中断产生后,系统根据中断异常从异常向量表中获取并执行中断异常流程,然后根据handler_arch_irq关联到对应平台的中断控制器,从中断控制器中得到中断号,然后执行对应的中断处理函数的大致流程就是这样了。

阅读(3066) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:中断子系统分析(二)

给主人留下些什么吧!~~