arm linux中断分析,ARM-Linux 中断分析

ARM体系结构中,把复位、中断、快速中断等都看作‘异常’,当这些‘异常’发生时,CPU会到固定地址处去找指令,他们对应的地址如下:

地址                异常类型      进入时的工作模式

0x00000000         Reset                 Supervisor

0x00000004         Und                  Undefined

0x00000008         Soft interupt           Supervisor

0x0000000c         Abort(prefetch)        Abort

0x00000010         Abort(data)            Abort

0x00000014         Reserved              Reserved

0x00000018         IRQ                   IRQ

0x0000001c         FIQ                   FIQ

首先要明确的一点就是,无论内存地址空间是如何映射的,以上这些地址都不会变,比如当有快速中断发生时,ARM将铁定到0X0000001C这个地址处取指令。这也是BOOTLOADER把操作系统引导以后,内存必须重映射的原因!否则操作系统不能真正接管整套系统!

LINUX启动以后要初始化这些区域,初始化代码在main.c中的start_kernel()中,具体是调用函数trap_ini()来实现的。如下面所示(具体可参照entry-armv.S):

.LCvectors:         swi    SYS_ERROR0

b       __real_stubs_start + (vector_undefinstr - __stubs_start)

ldr     pc, __real_stubs_start + (.LCvswi - __stubs_start)

b       __real_stubs_start + (vector_prefetch - __stubs_start)

b       __real_stubs_start + (vector_data - __stubs_start)

b       __real_stubs_start + (vector_addrexcptn - __stubs_start)

b       __real_stubs_start + (vector_IRQ - __stubs_start)

b       __real_stubs_start + (vector_FIQ - __stubs_start)

ENTRY(__trap_init)

stmfd         sp!, {r4 - r6, lr}

adr    r1, .LCvectors                        @ set up the vectors

ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}

stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}

add    r2, r0, #0x200

adr    r0, __stubs_start          @ copy stubs to 0x200

adr    r1, __stubs_end

1:                ldr     r3, [r0], #4

str     r3, [r2], #4

cmp  r0, r1

blt     1b

LOADREGS(fd, sp!, {r4 - r6, pc})

以上可以看出这个函数初始化了中断向量,实际上把相应的跳转指令拷贝到了对应的地址。

当发生中断时,不管是从用户模式还是管理模式调用的,最终都要调用do_IRQ():

__irq_usr:  sub    sp, sp, #S_FRAME_SIZE

stmia sp, {r0 - r12}                         @ save r0 - r12

ldr     r4, .LCirq

add    r8, sp, #S_PC

ldmia r4, {r5 - r7}                           @ get saved PC, SPSR

stmia r8, {r5 - r7}                           @ save pc, psr, old_r0

stmdb         r8, {sp, lr}^

alignment_trap r4, r7, __temp_irq

zero_fp

1:                get_irqnr_and_base r0, r6, r5, lr

movne        r1, sp

adrsvc        ne, lr, 1b

@

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

@

bne    do_IRQ    @ 调用do_IRQ来实现具体的中断处理

mov  why, #0

get_current_task tsk

b       ret_to_user

对于以上代码,在很多文章中都有过分析,这里不再赘述。

Linux每个中断通过一个结构irqdesc来描述,各中断的信息都在这个结构中得以体现:

struct irqdesc {

unsigned int         nomask   : 1;            /* IRQ does not mask in IRQ   */

unsigned int         enabled  : 1;              /* IRQ is currently enabled   */

unsigned int         triggered: 1;                 /* IRQ has occurred           */

unsigned int         probing  : 1;              /* IRQ in use for a probe     */

unsigned int         probe_ok : 1;              /* IRQ can be used for probe  */

unsigned int         valid    : 1;               /* IRQ claimable       */

unsigned int         noautoenable : 1;        /* don't automatically enable IRQ */

unsigned int         unused   :25;

void (*mask_ack)(unsigned int irq);         /* Mask and acknowledge IRQ   */

void (*mask)(unsigned int irq);                /* Mask IRQ                      */

void (*unmask)(unsigned int irq);   /* Unmask IRQ                  */

struct irqaction *action;

/*

* IRQ lock detection

*/

unsigned int         lck_cnt;

unsigned int         lck_pc;

unsigned int         lck_jif;

};

在具体的ARM芯片中会有很多的中断类型,每一种类型的中断用以上结构来表示:

struct irqdesc irq_desc[NR_IRQS];   /* NR_IRQS根据不同的MCU会有所区别*/

在通过request_irq()函数注册中断服务程序的时候,将会把中断向量和中断服务程序对应起来。

我们来看一下request_irq的源码:

int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),

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

{

unsigned long retval;

struct irqaction *action;

if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||

(irq_flags & SA_SHIRQ && !dev_id))

return -EINVAL;

action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);

if (!action)              /*  生成action结构*/

return -ENOMEM;

action->handler = handler;

action->flags = irq_flags;

action->mask = 0;

action->name = devname;

action->next = NULL;

action->dev_id = dev_id;

retval = setup_arm_irq(irq, action);   /*把中断号irq和action 对应起来*/

if (retval)

kfree(action);

return retval;

}

其中第一个参数irq就是中断向量,第二个参数即是要注册的中断服务程序。很多同仁可能疑惑的是,我们要注册的中断向量号是怎么确定的呢?这要根据具体芯片的中断控制器,比如三星的S3C2410,需要          通过读取其中的中断状态寄存器,来获得是哪个设备发生了中断:

if defined(CONFIG_ARCH_S3C2410)

#include

.macro  disable_fiq

.endm

.macro  get_irqnr_and_base, irqnr, irqstat, base, tmp

mov  r4, #INTBASE             @ virtual address of IRQ registers

ldr     \irqnr, [r4, #0x8]  @ read INTMSK   中断掩码寄存器

ldr     \irqstat, [r4, #0x10]   @ read INTPND  中断寄存器

bics    \irqstat, \irqstat, \irqnr

bics    \irqstat, \irqstat, \irqnr

beq    1002f

mov  \irqnr, #0

1001:                   tst     \irqstat, #1

bne    1002f                            @ found IRQ

add    \irqnr, \irqnr, #1

mov  \irqstat, \irqstat, lsr #1

cmp  \irqnr, #32

bcc    1001b

1002:

.endm

.macro  irq_prio_table

.endm

以上代码也告诉了我们,中断号的确定,其实是和S3C2410手册中SRCPND寄存器是一致的,即:

/* Interrupt Controller */

#define IRQ_EINT0             0       /* External interrupt 0 */

#define IRQ_EINT1             1       /* External interrupt 1 */

#define IRQ_EINT2             2       /* External interrupt 2 */

#define IRQ_EINT3             3       /* External interrupt 3 */

#define IRQ_EINT4_7                  4       /* External interrupt 4 ~ 7 */

#define IRQ_EINT8_23                5       /* External interrupt 8 ~ 23 */

#define IRQ_RESERVED6            6       /* Reserved for future use */

#define IRQ_BAT_FLT                 7

#define IRQ_TICK               8       /* RTC time tick interrupt  */

#define IRQ_WDT                        9       /* Watch-Dog timer interrupt */

#define IRQ_TIMER0                   10     /* Timer 0 interrupt */

#define IRQ_TIMER1                   11     /* Timer 1 interrupt */

#define IRQ_TIMER2                   12     /* Timer 2 interrupt */

#define IRQ_TIMER3                   13     /* Timer 3 interrupt */

#define IRQ_TIMER4                   14     /* Timer 4 interrupt */

#define IRQ_UART2           15     /* UART 2 interrupt  */

#define IRQ_LCD                          16     /* reserved for future use */

#define IRQ_DMA0            17     /* DMA channel 0 interrupt */

#define IRQ_DMA1            18     /* DMA channel 1 interrupt */

#define IRQ_DMA2            19     /* DMA channel 2 interrupt */

#define IRQ_DMA3            20     /* DMA channel 3 interrupt */

#define IRQ_SDI                           21     /* SD Interface interrupt */

#define IRQ_SPI0                 22     /* SPI interrupt */

#define IRQ_UART1           23     /* UART1 receive interrupt */

#define IRQ_RESERVED24          24

#define IRQ_USBD              25     /* USB device interrupt */

#define IRQ_USBH              26     /* USB host interrupt */

#define IRQ_IIC                            27     /* IIC interrupt */

#define IRQ_UART0           28     /* UART0 transmit interrupt */

#define IRQ_SPI1                 29     /* UART1 transmit interrupt */

#define IRQ_RTC                          30     /* RTC alarm interrupt */

#define IRQ_ADCTC          31     /* ADC EOC interrupt */

#define NORMAL_IRQ_OFFSET        32

这些宏定义在文件irqs.h中,大家可以看到它的定义取自S3C2410的文档。

总结: linux在初始化的时候已经把每个中断向量的地址准备好了!就是说添加中断服务程序的框架已经给出,当某个中断发生时,将会到确定的地址处去找指令,所以我们做驱动程序时,只需要经过request_irq()来挂接自己编写的中断服务程序即可。

另:对于快速中断,linux在初始化时是空的,所以要对它挂接中断处理程序,就需要单独的函数set_fiq_handler()来实现,此函数在源文件fiq.c中,有兴趣的读者可进一步研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值