基于ARM处理器的linux中断详解(结合linux2.6.32.2-GQ2440分析)

来源:互联网 发布:mt4编程论坛 编辑:程序博客网 时间:2024/06/04 17:55

 

系统初始化从init/main.c中的start_kernel()开始,调用setup_arch进行平台体系(处理器芯片)相关的初始化,然后复制中断向量表到内存中并对irq进行初始化:

/* init/main.c */asmlinkage void __init start_kernel(void){    ……    setup_arch(&command_line);    ……    trap_init();    ……    init_IRQ();    ……}


对于linux2.6.32.2-GQ2440的arm体系架构,trap_init()是空操作,实际的工作在setup_arch()末尾通过调用early_trap_init()完成:

void __init early_trap_init(void){unsigned long vectors = CONFIG_VECTORS_BASE;extern char __stubs_start[], __stubs_end[];extern char __vectors_start[], __vectors_end[];        ……        /* * 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);        ……}

CONFIG_VECTORS_BASE最初是在各个平台的配置文件中设定的,如GQ2440内核中的GQ2440_config中

CONFIG_VECTORS_BASE=0xffff0000

__vectors_end 至 __vectors_start之间为异常向量表。

位于arch/arm/kernel/entry-armv.S

__vectors_start: ARM(swiSYS_ERROR0) THUMB(svc#0) THUMB(nop)W(b)vector_und + stubs_offsetW(ldr)pc, .LCvswi + stubs_offsetW(b)vector_pabt + stubs_offsetW(b)vector_dabt + stubs_offsetW(b)vector_addrexcptn + stubs_offsetW(b)vector_irq + stubs_offsetW(b)vector_fiq + stubs_offset.globl__vectors_end__vectors_end:


__stubs_end 至 __stubs_start之间是异常处理的位置。也位于文件arch/arm/kernel/entry-armv.S中。vector_und、vector_pabt、vector_irq、vector_fiq都在它们中间。

 

stubs_offset值如下:

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

 

中断发生后,跳转到b vector_irq + stubs_offset的位置执行(向量表的初始位置是0xffff0000)


__stubs_start:/* * Interrupt dispatcher */vector_stubirq, 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


上面代码中vector_stub宏的定义为:

/* * Vector stubs. * * This code is copied to 0xffff0200 so we can use branches in the * vectors, rather than ldr's.  Note that this code must not * exceed 0x300 bytes. * * Common stub entry macro: *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC * * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. */.macrovector_stub, name, mode, correction=0.align5vector_\name:.if \correctionsublr, lr, #\correction.endif@@ Save r0, lr_<exception> (parent PC) and spsr_<exception>@ (parent CPSR)@stmiasp, {r0, lr}@ save r0, lrmrslr, spsrstrlr, [sp, #8]@ save spsr@@ Prepare for SVC32 mode.  IRQs remain disabled.@mrsr0, cpsreorr0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)msrspsr_cxsf, r0@@ the branch table must immediately follow this code@andlr, lr, #0x0f    
 THUMB(adrr0, 1f) THUMB(ldrlr, [r0, lr, lsl #2])movr0, sp ARM(ldrlr, [pc, lr, lsl #2])movspc, lr@ branch to handler in SVC modeENDPROC(vector_\name)

 

上面的代码先将进入中断前spsr记录到lr中,在通过and lr, lr, #0x0f 获得表示mode的后4位:

             @#define USR_MODE 0x00000010             @#define FIQ_MODE 0x00000011             @#define IRQ_MODE 0x00000012             @#define SVC_MODE 0x00000013             @#define ABT_MODE 0x00000017             @#define UND_MODE 0x0000001b             @#define SYSTEM_MODE 0x0000001f

 接着利用 ldr lr, [pc, lr, lsl #2] 和movs pc, lr指令进行跳转。 进入中断前是USR模式说明这是一个用户空间的中断,如果进入前是SVC模式,则说明是内核空间的中断。

 以用户空间中断为例,如果发生中断,这时应该跳转到同文件(arch/arm/kernel/entry-armv.S)中的如下函数执行:

__irq_usr:usr_entrykuser_cmpxchg_checkget_thread_info tsk#ifdef CONFIG_PREEMPTldrr8, [tsk, #TI_PREEMPT]@ get preempt countaddr7, r8, #1@ increment itstrr7, [tsk, #TI_PREEMPT]#endifirq_handler#ifdef CONFIG_PREEMPTldrr0, [tsk, #TI_PREEMPT]strr8, [tsk, #TI_PREEMPT]teqr0, r7 ARM(strner0, [r0, -r0]) THUMB(movner0, #0) THUMB(strner0, [r0])#endif#ifdef CONFIG_TRACE_IRQFLAGSbltrace_hardirqs_on#endifmovwhy, #0bret_to_user UNWIND(.fnend)ENDPROC(__irq_usr)


get_thread_info tsk 获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk等于r9(在entry-header.S中定义)

irq_handler 中断处理,是我们最关心的地方

b ret_to_user 中断处理完成,返回中断产生的位置(用户空间)

同样在这个文件中irq_handler的实现如下:

/* * Interrupt handling.  Preserves r7, r8, r9 */.macroirq_handlerget_irqnr_preamble r5, lr1:get_irqnr_and_base r0, r6, r5, lrmovner1, sp@@ routine called with r0 = irq number, r1 = struct pt_regs *@adrnelr, BSYM(1b)bneasm_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, lrmovner0, spadrnelr, BSYM(1b)bnedo_IPI#ifdef CONFIG_LOCAL_TIMERStest_for_ltirq r0, r6, r5, lrmovner0, spadrnelr, BSYM(1b)bnedo_local_timer#endif#endif.endm


 get_irqnr_and_base获得中断号

 bne asm_do_IRQ 进入相应的中断处理

 get_irqnr_and_base定义在arch/arm/mach-s3c2410/include/mach/entry-macro.S中,内容如下:

.macroget_irqnr_and_base, irqnr, irqstat, base, tmpmov\base, #S3C24XX_VA_IRQ@@ try the interrupt offset register, since it is thereldr\irqstat, [ \base, #INTPND ]teq\irqstat, #0beq1002fldr\irqnr, [ \base, #INTOFFSET ]mov\tmp, #1tst\irqstat, \tmp, lsl \irqnrbne1001f@@ the number specified is not a valid irq, so try@@ and work it out for ourselvesmov\irqnr, #0@@ start here@@ work out which irq (if any) we gotmovs\tmp, \irqstat, lsl#16addeq\irqnr, \irqnr, #16moveq\irqstat, \irqstat, lsr#16tst\irqstat, #0xffaddeq\irqnr, \irqnr, #8moveq\irqstat, \irqstat, lsr#8tst\irqstat, #0xfaddeq\irqnr, \irqnr, #4moveq\irqstat, \irqstat, lsr#4tst\irqstat, #0x3addeq\irqnr, \irqnr, #2moveq\irqstat, \irqstat, lsr#2tst\irqstat, #0x1addeq\irqnr, \irqnr, #1@@ we have the value1001:adds\irqnr, \irqnr, #IRQ_EINT01002:@@ exit here, Z flag unset if IRQ.endm


该函数可以通过INTPND和 INTOFFSET寄存器 确定中断号,并保存在\irqnr中(也就是调用时传入的r0寄存器)该中断号的大小和INTOFFSET寄存器的值应该是对应的(多了#IRQ_EINT0作为偏移,IRQ_EINT0在arch/arm/mach-s3c2410/include/mach/irqs.h中定义)

注意:此时没有考虑子中断的具体情况,中断号的具体值是有平台相关的代码决定的,和硬件中断挂起寄存器中的中断号是不等的。

asm_do_IRQ实现在arch/arm/kernel/irq.c中,内容如下:

/* * do_IRQ handles all hardware IRQ's.  Decoded IRQs should not * come via this function.  Instead, they should provide their * own 'handler' */asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs){struct pt_regs *old_regs = set_irq_regs(regs);irq_enter();/* * Some hardware gives randomly wrong interrupts.  Rather * than crashing, do something sensible. */if (unlikely(irq >= NR_IRQS)) {if (printk_ratelimit())printk(KERN_WARNING "Bad IRQ%u\n", irq);ack_bad_irq(irq);} else {generic_handle_irq(irq);}/* AT91 specific workaround */irq_finish(irq);irq_exit();set_irq_regs(old_regs);}


其中通过generic_handle_irq(irq)(include/linux/irq.h)调用对应中断号的中断处理函数:

/* * Architectures call this to let the generic IRQ layer * handle an interrupt. If the descriptor is attached to an * irqchip-style controller then we call the ->handle_irq() handler, * and it calls __do_IRQ() if it's attached to an irqtype-style controller. */static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc){#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQdesc->handle_irq(irq, desc);#elseif (likely(desc->handle_irq))desc->handle_irq(irq, desc);else__do_IRQ(irq);#endif}static inline void generic_handle_irq(unsigned int irq){generic_handle_irq_desc(irq, irq_to_desc(irq));}


要明白这两个函数的含义,需要清楚下面几个重要的结构体:

(1)中断描述符

 对于每一条中断线都由一个irq_desc结构来描述(include/linux/irq.h)。

/** * struct irq_desc - interrupt descriptor * @irq:interrupt number for this descriptor * @timer_rand_state:pointer to timer rand state struct * @kstat_irqs:irq stats per cpu * @irq_2_iommu:iommu with this irq * @handle_irq:highlevel irq-events handler [if NULL, __do_IRQ()] * @chip:low level interrupt hardware access * @msi_desc:MSI descriptor * @handler_data:per-IRQ data for the irq_chip methods * @chip_data:platform-specific per-chip private data for the chip *methods, to allow shared chip implementations * @action:the irq action chain * @status:status information * @depth:disable-depth, for nested irq_disable() calls * @wake_depth:enable depth, for multiple set_irq_wake() callers * @irq_count:stats field to detect stalled irqs * @last_unhandled:aging timer for unhandled count * @irqs_unhandled:stats field for spurious unhandled interrupts * @lock:locking for SMP * @affinity:IRQ affinity on SMP * @node:node index useful for balancing * @pending_mask:pending rebalanced interrupts * @threads_active:number of irqaction threads currently running * @wait_for_threads:wait queue for sync_irq to wait for threaded handlers * @dir:/proc/irq/ procfs entry * @name:flow handler name for /proc/interrupts output */struct irq_desc {unsigned intirq;             //中断号struct timer_rand_state *timer_rand_state;unsigned int            *kstat_irqs;#ifdef CONFIG_INTR_REMAPstruct irq_2_iommu      *irq_2_iommu;#endifirq_flow_handler_t          handle_irq;     //上层中断处理函数struct irq_chip*chip;           //底层硬件操作struct msi_desc*msi_desc;      void*handler_data;   //handle_irq附加参数void*chip_data;      //平台相关参数,用于chipstruct irqaction*action;/* IRQ action list 中断服务例程链表*/ unsigned intstatus;/* IRQ status */

//中断关闭打开层数调用一次disable_irq( ) ,depth加1;调用一次enable_irq( )该值减1,

//如果depth等于0就开启这条中断线,如果depth大于0就关闭中断线。unsigned intdepth;/* nested irq disables */unsigned intwake_depth;/* nested wake enables */unsigned intirq_count;/* For detecting broken IRQs */unsigned longlast_unhandled;/* Aging timer for unhandled count */unsigned intirqs_unhandled;spinlock_tlock;#ifdef CONFIG_SMPcpumask_var_taffinity;unsigned intnode;#ifdef CONFIG_GENERIC_PENDING_IRQcpumask_var_tpending_mask;#endif#endifatomic_tthreads_active;wait_queue_head_t wait_for_threads;#ifdef CONFIG_PROC_FSstruct proc_dir_entry*dir;#endifconst char*name;} ____cacheline_internodealigned_in_smp;


在kernel/irq/handle.c中有个全局irq_desc数组,描述了系统中所有的中断线:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = { [0 ... NR_IRQS-1] = {  .status = IRQ_DISABLED,  .chip = &no_irq_chip,  .handle_irq = handle_bad_irq,  .depth = 1,  .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock), }};

 

NR_IRQS为最大中断数对于s3c2440芯片,在文件arch/arm/mach-s3c2410/include/mach/irqs.h中定义如下:

 

#ifdef CONFIG_CPU_S3C2443#define NR_IRQS (IRQ_S3C2443_AC97+1)#else#define NR_IRQS (IRQ_S3C2440_AC97+1)   //每一个中断源都对应一个irq_desc结构体。#endif


(2)中断硬件操作函数集
在include/linux/irq.h中定义,该结构体中各函数在文件arch/arm/plat-s3c24xx/irq.c中实现:

struct irq_chip { const char *name; //用于 /proc/interrupts unsigned int (*startup)(unsigned int irq); //默认为 enable 如果为NULL void  (*shutdown)(unsigned int irq); //默认为 disable 如果为NULL void  (*enable)(unsigned int irq); //允许中断,默认为 unmask 如果为NULL void  (*disable)(unsigned int irq); //禁止中断,默认为 mask 如果为 NULL void  (*ack)(unsigned int irq); //响应一个中断,清除中断标志 void  (*mask)(unsigned int irq); //mask 一个中断源,通常是关闭中断 void  (*mask_ack)(unsigned int irq); //响应并 mask 中断源 void  (*unmask)(unsigned int irq); //unmask 中断源 void  (*eoi)(unsigned int irq); void  (*end)(unsigned int irq); void  (*set_affinity)(unsigned int irq,     const struct cpumask *dest); int  (*retrigger)(unsigned int irq); int  (*set_type)(unsigned int irq, unsigned int flow_type); //设置中断触发方式 IRQ_TYPE_LEVEL int  (*set_wake)(unsigned int irq, unsigned int on); /* Currently used only by UML, might disappear one day.*/#ifdef CONFIG_IRQ_RELEASE_METHOD void  (*release)(unsigned int irq, void *dev_id);#endif /*  * For compatibility, ->typename is copied into ->name.  * Will disappear.  */ const char *typename;};

(3)中断处理例程描述符

在include/linux/interrupt.h中定义:

struct irqaction { irq_handler_t handler; /* 具体的中断处理程序 */ unsigned long flags; //用一组标志描述中断线与 I/O 设备之间的关系。 cpumask_t mask; const char *name; /* 名称,会显示在/proc/interreupts中 */ void *dev_id; /* 设备ID,用于区分共享一条中断线的多个处理程序 ,以便从共享中断线的诸多中断处理程序中删除指定的那一个*/ struct irqaction *next; /* 指向下一个irq_action结构 */ int irq;  /* 中断通道号 */ struct proc_dir_entry *dir;  /* procfs目录 */ irq_handler_t thread_fn; struct task_struct *thread; unsigned long thread_flags;};


三个结构体的关系如下:

 

明白了这几个重要结构体及其关系后, 再回头看generic_handle_irq(irq),可知其主要是通过desc->handle_irq(irq, desc);来执行对应的中断处理函数的。

 

这时在回到文章最开头初始化的部分,在s3c2440芯片中断的初始化中就完成了各个desc的handle_irq成员的初始化:

init_IRQ()在arch/arm/kernel/irq.c中实现:

void __init init_IRQ(void){   int irq;   /*  设置 irq_desc 数组的 status 为 IRQ_NOREQUEST | IRQ_NOPROBE(没有请求,没有检测)  */   for (irq = 0; irq < NR_IRQS; irq++)   irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;#ifdef CONFIG_SMP   cpumask_setall(bad_irq_desc.affinity);   bad_irq_desc.cpu = smp_processor_id();#endif   /*   init_arch_irq在文件linux/arch/arm/kernel/irq.c中定义如下    void (*init_arch_irq)(void) __initdata = NULL;   该函数指针 在 setup_arch()中被赋值,    init_arch_irq = mdesc->init_irq;   指向 machine_desc 中定义的 init_irq 函数。    在平台GQ2440中,该函数在文件linux/arch/arm/plat-s3c24xx/irq.c中实现(s3c24xx_init_irq)。   */   init_arch_irq();}


函数s3c24xx_init_irq在文件linux/arch/arm/plat-s3c24xx/irq.c中实现

void __init s3c24xx_init_irq(void){   unsigned long pend;   unsigned long last;   int irqno;   int i;   irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");   /* first, clear all interrupts pending... */   last = 0;   for (i = 0; i < 4; i++) {      pend = __raw_readl(S3C24XX_EINTPEND);      if (pend == 0 || pend == last)        break;      __raw_writel(pend, S3C24XX_EINTPEND); //清除外部中断寄存器EINTPEND中的请求标志,       printk("irq: clearing pending ext status %08x\n", (int)pend);      last = pend;   }   last = 0;   ......     //设置各中断的底层硬件操作函数集desc->chip,中断上层处理函数desc->handle_irq    for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {    /* set all the s3c2410 internal irqs */    switch (irqno) {    /* deal with the special IRQs (cascaded) */    case IRQ_EINT4t7:    case IRQ_EINT8t23:    case IRQ_UART0:    case IRQ_UART1:    case IRQ_UART2:    case IRQ_ADCPARENT:       set_irq_chip(irqno, &s3c_irq_level_chip);       set_irq_handler(irqno, handle_level_irq);       break;    case IRQ_RESERVED6:    case IRQ_RESERVED24:    /* no IRQ here */       break;       default:    //irqdbf("registering irq %d (s3c irq)\n", irqno);    set_irq_chip(irqno, &s3c_irq_chip);    set_irq_handler(irqno, handle_edge_irq);    set_irq_flags(irqno, IRQF_VALID);    }  }  //以下几个中断都有多个中断源,每一个中断源也都有各自的中断号,它们的多个中断源中任意一个产生中断  //该中断都会被触发,而不是直接出发子中断。这几个中断并不处理中断函数,它们的中作是计算子中断的中断号,  //并根据子中断的中断号在数组irq_desc[NR_IRQS]中去找出该中断号对应的irq_desc结构,并调用该结构中的中断处理函数。  set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);  set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);  set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);  set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);  set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);  set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);   ......   for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {  irqdbf("registering irq %d (extended s3c irq)\n", irqno);  set_irq_chip(irqno, &s3c_irqext_chip);  //设置子中断的硬件操作函数集  set_irq_handler(irqno, handle_edge_irq); //设置子中断的上层处理函数  set_irq_flags(irqno, IRQF_VALID);  }  ...... }

 

比如此时外部中断10产生了中断,中断号为IRQ_EINT8t23的中断被触发,执行函数s3c_irq_demux_extint8()。

static voids3c_irq_demux_extint8(unsigned int irq,        struct irq_desc *desc){ unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND); unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK); eintpnd &= ~eintmsk; eintpnd &= ~0xff; /* ignore lower irqs */ /* we may as well handle all the pending IRQs here */ while (eintpnd) {  irq = __ffs(eintpnd);  //计算该中断在外部中断寄存器EINTPEND中的偏移量  eintpnd &= ~(1<<irq);  irq += (IRQ_EINT4 - 4); //根据这个偏移量重新计算中断号  generic_handle_irq(irq); //根据重新计算的中断号获取对应的结构体irq_desc,并调用它的上层中断处理函数。 }}

 

上层中断处理函数

上层中断处理函数有5个分别为:handle_simple_irq(),handle_level_irq(),
handle_edge_irq(),handle_fasteoi_irq()以及handle_percpu_irq()。

这几个函数在文件kernel/irq/chip.c中实现。常用的有两个handle_level_irq(),和handle_edge_irq()。

这5个上层中断处理函数都是通过调用函数handle_IRQ_event()来做进一步处理。

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action){ irqreturn_t ret, retval = IRQ_NONE; unsigned int status = 0; if (!(action->flags & IRQF_DISABLED))  local_irq_enable_in_hardirq(); do {  ......  ret = action->handler(irq, action->dev_id); //执行中断处理函数。  ......   retval |= ret;  action = action->next; } while (action); //调用该中断线上的所有例程 if (status & IRQF_SAMPLE_RANDOM)  add_interrupt_randomness(irq); local_irq_disable(); return retval;} voidhandle_level_irq(unsigned int irq, struct irq_desc *desc){ struct irqaction *action; irqreturn_t action_ret; ...... desc = irq_remap_to_desc(irq, desc); ...... action = desc->action; action_ret = handle_IRQ_event(irq, action); ......}voidhandle_edge_irq(unsigned int irq, struct irq_desc *desc){ spin_lock(&desc->lock); ......
  desc = irq_remap_to_desc(irq, desc); ......  desc->status |= IRQ_INPROGRESS; do {  struct irqaction *action = desc->action; ......   desc->status &= ~IRQ_PENDING;  spin_unlock(&desc->lock);  action_ret = handle_IRQ_event(irq, action);  if (!noirqdebug)   note_interrupt(irq, desc, action_ret);  spin_lock(&desc->lock);//该函数与函数handle_level_irq不太一样的是,该函数多了一个循环。即如果在本次中断//的处理过程中该中断线上又有中断产生,则再次执行该中断线上的处理例程/*以下是5个常用的中断线状态。#define IRQ_INPROGRESS 1 /* 正在执行这个 IRQ 的一个处理程序 */#define IRQ_DISABLED 2 /* 由设备驱动程序已经禁用了这条 IRQ 中断线 */#define IRQ_PENDING 4 /* 一个 IRQ 已经出现在中断线上,且被应答,但还没有为它提供服务 */#define IRQ_REPLAY 8 /* 当 Linux 重新发送一个已被删除的 IRQ 时 */#define IRQ_WAITING 32 /* 当对硬件设备进行探测时,设置这个状态以标记正在被测试的 irq */*/ } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING); desc->status &= ~IRQ_INPROGRESS;out_unlock: spin_unlock(&desc->lock);}

思考:

1.handle_level_irq 和handle_edge_irq的区别

2.分析内核request_irq的实现


 


	
				
		
原创粉丝点击