Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1039362
  • 博文数量: 244
  • 博客积分: 6820
  • 博客等级: 准将
  • 技术积分: 3020
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-09 21:33
文章分类

全部博文(244)

文章存档

2013年(1)

2012年(16)

2011年(132)

2010年(3)

2009年(12)

2008年(80)

我的朋友

分类:

2008-09-12 20:37:48

             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中,有兴趣的读者可进一步研究。

阅读(833) | 评论(5) | 转发(0) |
给主人留下些什么吧!~~

UP-NETARM24102008-09-15 14:01:09

你写驱动的时候,初始化时申请了中断号,在卸载驱动模块时,要注销中断,也就是清除相应的中断,用这个函数void free_irq(unsigned int irq, void *dev_id),你还是多看看基础吧

chinaunix网友2008-09-14 12:01:47

谢谢,发现是我用的一个函数有问题,还有就是ARM LINUX内如何清除外部中断,有没有这方面的函数呢?

UP-NETARM24102008-09-13 10:18:26

给你个例子 ret = request_irq(IRQ_TC, s3c2410_isr_tc,SA_INTERRUPT, DEVICE_NAME, s3c2410_isr_tc); if (ret) { return ret; }

UP-NETARM24102008-09-13 10:15:41

不用,linux已经替你做好了,不知道是否中断申请成功了,你看看返回值是不是0

chinaunix网友2008-09-13 09:02:40

你好,我写了个中断测试的模块,怎么request_irq(IRQ_EINT0,&irq_handler,SA_INTERRUPT,"irq",NULL)后,中断测试没反应?还要另外开启中断吗