现在的位置: 首页 > 综合 > 正文

linux中断程序分析

2018年02月07日 ⁄ 综合 ⁄ 共 15086字 ⁄ 字号 评论关闭
一、数据结构 
中断机制的核心数据结构是 irq_desc, 它完整地描述了一条中断线 (或称为 “中断通道” )。irq_desc 结构在 include/linux/irq.h 中定义: 
 
typedef void fastcall (*irq_flow_handler_t)(unsigned int irq, struct irq_desc *desc); 
struct irq_desc { 
    irq_flow_handler_t handle_irq; /* 高层次的中断事件处理函数 */ 
    struct irq_chip *chip; /* 低层次的硬件操作 */ 
    struct msi_desc *msi_desc; /* MSI 描述符?? */ 
    void *handler_data; /* chip 方法使用的数据*/ 
    void *chip_data; /* chip 私有数据 */ 
    struct irqaction *action; /* 行为链表(action list) */ 
    unsigned int status; /* 状态 */ 
    unsigned int depth; /* 关中断次数 */ 
    unsigned int wake_depth; /* 唤醒次数 */ 
    unsigned int irq_count; /* 发生的中断次数 */ 
    unsigned int irqs_unhandled; 
    spinlock_t lock; /* 自旋锁 */ 
#ifdef CONFIG_SMP 
    cpumask_t affinity; 
    unsigned int cpu; 
#endif 
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE) 
    cpumask_t pending_mask; 
#endif 
#ifdef CONFIG_PROC_FS 
    struct proc_dir_entry *dir; /* 在 proc 文件系统中的目录 */ 
#endif 
    const char *name; /* 名称 */ 
} ____cacheline_aligned; 
    
在 kernel/irq/handle.c中有个全局 irq_desc 数组,描述了系统中所有的中断线: 
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned = { 
    [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), 
#ifdef CONFIG_SMP 
    .affinity = CPU_MASK_ALL 
#endif 
    } 
}; 
 
数组共有 NR_IRQS 个元素,每个元素对应一条中断线。其中 NR_IRQS 是系统中的中断线的数量,对于davinci平台,这个值为245=101+144。 
  
首先是 handle_irq,这是个函数指针,指向的是一个高层次的中断事件处理函数,定义了处理中断事件的一种策略。 
在 kernel/irq/chip.c 中实现了 5 个函数:handle_simple_irq(),handle_level_irq(), handle_edge_irq(),handle_fasteoi_irq()以及 handle_percpu_irq()。 handle_irq 指针可以指向这5个函数中的一个, 选择一种中断事件处理策略, 这是通过函数set_irq_handler()完成的。 
 
接下来是 chip, 这是个 irq_chip 结构指针。 irq_chip结构定义了对中断线的底层硬件操作,在 include/linux/irq.h 中: 
struct irq_chip { 
    const char *name; 
    unsigned int (*startup)(unsigned int irq); 
    void (*shutdown)(unsigned int irq); 
    void (*enable)(unsigned int irq); 
    void (*disable)(unsigned int irq);     
    void (*ack)(unsigned int irq); /* 确认中断 */ 
    void (*mask)(unsigned int irq); /* 屏蔽中断 */ 
    void (*mask_ack)(unsigned int irq); /* 确认并屏蔽中断 */ 
    void (*unmask)(unsigned int irq); /* 取消屏蔽中断 */ 
    void (*eoi)(unsigned int irq); /* 中断结束 */ 
    void (*end)(unsigned int irq); 
    void (*set_affinity)(unsigned int irq, cpumask_t dest); 
    int (*retrigger)(unsigned int irq); /* 重新触发中断 */ 
    int (*set_type)(unsigned int irq, unsigned int flow_type); /* 设置中断触发类型:高电平/低电平/上升沿/下降沿 */ 
    int (*set_wake)(unsigned int irq, unsigned int on); /* 唤醒中断*/ 
    … … 
}; 
  
最后的数据成员是 action,这是个 irq_action 结构指针。irq_action 结构定义了安装在中断线上的一个中断处理程序,在 include/linux/interrupt.h中: 
struct irqaction { 
    irq_handler_t handler; /* 具体的中断处理程序 */ 
    unsigned long flags; 
    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 目录 */ 
}; 
多个中断处理程序可以共享同一条中断线,irqaction 结构中的 next 成员用来把共享同一条中断线的所有中断处理程序组成一个单向链表,dev_id 成员用于区分各个中断处理程序。 
综合起来,可以得出中断机制各个数据结构之间的联系,如下图:


二、中断初始化
中断机制的初始化通过 start_kernel()中的两个函数完成:trap_init()和 init_IRQ()。
//trap_init()在 arch/arm/kernel/traps.c 中:
void __init trap_init(void)
{
    return;
}

//init_IRQ()在 arch/arm/kernel/irq.c 中:
void __init init_IRQ(void)
{
    init_arch_irq();
}
//init_arch_irq 在 setup_arch()中被赋值,指向machine_desc 中定义的init_irq 函数。在davinci平台上,就指向了arch/arm/mach-davinci/cp_intc.c 中定义的函数cp_intc_init(void)
/*
在board-da850-evm.c文件末尾有一段宏
MACHINE_START(DAVINCI_DA850_EVM, \"DaVinci DA850/OMAP-L138/AM18x EVM\")
       .boot_params = (DA8XX_DDR_BASE + 0x100),
       .map_io = da850_evm_map_io,
       .init_irq = cp_intc_init,
       .timer = &davinci_timer,
       .init_machine = da850_evm_init,
MACHINE_END

这里定义了linux的启动地址,IO口复用da850_evm_map_io,中断初始化函数cp_intc_init,板级初始化函数da850_evm_init(),即内核的setup.c和main.c是通过这个宏接受了板级的配置来进行初始化的。
内核分别调用了和配置了MACHINE_START宏中的函数和参数,主要是static void __init da850_evm_map_io(void)和static __init void da850_evm_init(void)这两个函数主要是对omapl138管脚的复用映射和对整个板级电路各个模块如uart、spi、vpif、lcd等在内核中的注册和初始化。
*/
void __init cp_intc_init(void)
{
    unsigned long num_irq    = davinci_soc_info.intc_irq_num;//中断数量101个
    u8 *irq_prio        = davinci_soc_info.intc_irq_prios;//得到中断优先级数组
    u32 *host_map        = davinci_soc_info.intc_host_map;
    unsigned num_reg    = BITS_TO_LONGS(num_irq);
    int i;

    davinci_intc_type = DAVINCI_INTC_TYPE_CP_INTC;//INTC中断类型#define DAVINCI_INTC_TYPE_CP_INTC 1
    //.intc_base        = DA8XX_CP_INTC_BASE,//中断控制器AINTC基地址 #define DA8XX_CP_INTC_BASE 0xfffee000
    davinci_intc_base = ioremap(davinci_soc_info.intc_base, SZ_8K);//将中断控制器AINTC物理基地址映射到内存空间
    if (WARN_ON(!davinci_intc_base))
        return;
    
    //CP_INTC_GLOBAL_ENABLE=0x10写的时候会加上基地址davinci_intc_base,就是写寄存器GER(Global Enable Register 地址:0xFFFEE010)
    cp_intc_write(0, CP_INTC_GLOBAL_ENABLE);//Disable所有中断

    /* Disable all host interrupts */
    cp_intc_write(0, CP_INTC_HOST_ENABLE(0));

    //Disable所有的系统中断
    for (i = 0; i < num_reg; i++)
        cp_intc_write(~0, CP_INTC_SYS_ENABLE_CLR(i));

    /* Set to normal mode, no nesting, no priority hold */
    cp_intc_write(0, CP_INTC_CTRL);//设置中断没有优先级、没有嵌套
    cp_intc_write(0, CP_INTC_HOST_CTRL);

    //清空系统中断标志
    for (i = 0; i < num_reg; i++)
        cp_intc_write(~0, CP_INTC_SYS_STAT_CLR(i));

    //使能IRQ中断类型(Disable FIQ)
    cp_intc_write(1, CP_INTC_HOST_ENABLE_IDX_SET);

    num_reg = (num_irq + 3) >> 2;    //104>>2=26 /* 4 channels per register */
    if (irq_prio) {//中断优先级结构
        unsigned j, k;
        u32 val;

        for (k = i = 0; i < num_reg; i++) {
            for (val = j = 0; j < 4; j++, k++) {
                val >>= 8;
                if (k < num_irq)
                    val |= irq_prio[k] << 24;//irq_prio[N]=7
            }

            cp_intc_write(val, CP_INTC_CHAN_MAP(i));
        }
    } else    {
        for (i = 0; i < num_reg; i++)
            cp_intc_write(0x0f0f0f0f, CP_INTC_CHAN_MAP(i));
    }

    if (host_map)//不存在
        for (i = 0; host_map[i] != -1; i++)
            cp_intc_write(host_map[i], CP_INTC_HOST_MAP(i));

    /* Set up genirq dispatching for cp_intc */
    for (i = 0; i < num_irq; i++) {
        set_irq_chip(i, &cp_intc_irq_chip);//设置中断线的底层硬件操作,desc->chip = cp_intc_irq_chip;
        set_irq_flags(i, IRQF_VALID | IRQF_PROBE);//设置中断标志
        set_irq_handler(i, handle_edge_irq);//设置每个中断的处理函数handle_edge_irq,即desc->handle_irq = handle_edge_irq;
    }
    /*static struct irq_chip cp_intc_irq_chip = {
        .name        = \"cp_intc\",
        .ack        = cp_intc_ack_irq,
        .mask        = cp_intc_mask_irq,
        .unmask        = cp_intc_unmask_irq,
        .set_type    = cp_intc_set_irq_type,
        .set_wake    = cp_intc_set_wake,
    };
    */
    //使能全部中断
    cp_intc_write(1, CP_INTC_GLOBAL_ENABLE);
}
//至此,中断机制的初始化工作已完成。

三、中断处理过程 
(1) 汇编语言部分 
当 ARM 处理器发生异常(中断是一种异常)时,会跳转到异常向量表(起始地址为0xFFFF_0000 或 0x0000_0000)。在中断机制的初始化过程中,把在arch/arm/kernel/entry-armv.S 中的异常向量表及其处理程序的stub重定位到了 0xFFFF_0000处,这样才使得 ARM 处理器能够正常处理异常,而且事实上执行的就是 entry-armv.S 中的异常处理程序。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 //中断入口:vector_irq 
    W(b)    vector_fiq + stubs_offset

    .globl    __vectors_end
__vectors_end:

vector_irq: 
//调整 LR_irq 
sub lr, lr, #4 
  
//保存 R0, LR_irq(中断之前的 PC, 断点), SPSR_irq(中断之前的 CPSR) 到 irq模式的栈中 
 stmia sp, {r0, lr} @ save r0, lr 
 mrs lr, spsr 
 str lr, [sp, #8] @ save spsr 
 
//SPSR 设置为 SVC模式 
 mrs r0, cpsr 
 eor r0, r0, #(\\mode ^ SVC_MODE) 
 msr spsr_cxsf, r0 
//根据中断前的模式跳转到相应的处理程序 
//lr是中断刚开始时的 SPSR,即被中断代码的 CPSR,其低 4位表示中断之前的模式
 and lr, lr, #0x0f 
 mov r0, sp 
 ldr lr, [pc, lr, lsl #2] 
 
//跳转到相应模式的处理程序,模式变为 SVC(SPSR 拷贝到 CPSR ) 
 movs pc, lr 
 
//跳转表,必须紧跟 ldr lr,[pc,lr,lsl #2]和 movs pc,lr 两条指令(ARM 流水线机制) 
  .long __irq_usr @ 0 (USR) 
 .long __irq_invalid @ 1 (FIQ) 
 .long __irq_invalid @ 2 (IRQ) 
 .long __irq_svc @ 3 (SVC) 
 .long __irq_invalid @ 4 
 .long __irq_invalid @ 5 
 .long __irq_invalid @ 6 (ABT) 
 .long __irq_invalid @ 7 
 .long __irq_invalid @ 8 
 .long __irq_invalid @ 9 
 .long __irq_invalid @ a 
 .long __irq_invalid @ b (UND) 
 .long __irq_invalid @ c 
 .long __irq_invalid @ d 
 .long __irq_invalid @ e 
 .long __irq_invalid @ f (SYS) 
 
//USR模式中断入口 
__irq_usr: 
     
//在内核栈中产生 include/asm-arm/ptrace.h中 pt_regs 定义的栈帧结构 
 sub sp, sp, #S_FRAME_SIZE 
 stmib sp, {r1 - r12} 
 ldmia r0, {r1 - r3} 
 add r0, sp, #S_PC @ here for interlock avoidance 
 mov r4, #-1 @ \"\" \"\" \"\" \"\" 
 str r1, [sp] @ save the \"real\" r0 copied 
 stmia r0, {r2 - r4} 
 stmdb r0, {sp, lr}^ 
 
//Clear FP to mark the first stack frame 
 zero_fp 
 
//把被中断任务的 preempt_count 增加 1 
 get_thread_info tsk 
#ifdef CONFIG_PREEMPT 
 ldr r8, [tsk, #TI_PREEMPT] @ get preempt count 
 add r7, r8, #1 @ increment it 
 str r7, [tsk, #TI_PREEMPT] 
#endif 
 
//循环调用 asm-do_IRQ() 
1: get_irqnr_and_base r0, r6, r5, lr 
 movne r1, sp 
 adrne lr, 1b 
 bne asm_do_IRQ 
 
#ifdef CONFIG_PREEMPT 
 ldr r0, [tsk, #TI_PREEMPT] 
 str r8, [tsk, #TI_PREEMPT] 
 teq r0, r7 
 strne r0, [r0, -r0] 
#endif 
//返回到 user 模式 
 mov why, #0 
 b ret_to_user 
 
//SVC模式中断入口 
__irq_svc: 
//在内核栈中产生 include/asm-arm/ptrace.h中 pt_regs 定义的栈帧结构 
 sub sp, sp, #S_FRAME_SIZE 
 tst sp, #4 
 bicne sp, sp, #4 
 stmib sp, {r1 - r12} 
 
 ldmia r0, {r1 - r3} 
 add r5, sp, #S_SP @ here for interlock avoidance 
 mov r4, #-1 @ \"\" \"\" \"\" \"\" 
 add r0, sp, #S_FRAME_SIZE @ \"\" \"\" \"\" \"\" 
 addne r0, r0, #4 
 str r1, [sp] @ save the \"real\" r0 copied from the exception stack 
 mov r1, lr 
 stmia r5, {r0 - r4} 
 
//把被中断任务的 preempt_count 增加 1 
#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 
 
//循环调用 asm-do_IRQ() 
1: get_irqnr_and_base r0, r6, r5, lr 
 movne r1, sp 
 adrne lr, 1b 
 bne asm_do_IRQ 
 
//如果需要调度,调用 svc_preempt进行内核抢占 
#ifdef CONFIG_PREEMPT 
 ldr r0, [tsk, #TI_FLAGS] @ get flags 
 tst r0, #_TIF_NEED_RESCHED 
 blne svc_preempt 
preempt_return: 
 ldr r0, [tsk, #TI_PREEMPT] @ read preempt value 
 str r8, [tsk, #TI_PREEMPT] @ restore preempt count 
 teq r0, r7 
 strne r0, [r0, -r0] @ bug() 
#endif 
//返回到内核空间 
 ldr r0, [sp, #S_PSR] @ irqs are already disabled 
 msr spsr_cxsf, r0 
 ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr 

//可以看到,中断的入口点是 vector_irq。此时处理器处于 irq 模式,vector_irq 首先把 R0,LR(中断发生之前的 PC)和 SPSR(中断发生之前的 CPSR)保存到 irq 模式的堆栈中,然后根据进入中断之前的处理器模式跳转到不同的入口,跳转后处理器的模式变成了 svc。 如果中断前处于 usr模式,跳转到__irq_usr;如果中断前处于 svc 模式,则跳转到__irq_svc。__irq_usr 和__irq_svc 运行流程大致相同:都是先在 svc 模式栈(也就是 linux 的内核栈)中生成/include/asm-arm/ptrace.h 中 struct pt_regs 定义的堆帧结构,把被中断任务的preempt_count增加1,然后通过宏 get_irqnr_and_base 读取中断通道号和中断状态寄存器,调用 asm_do_IRQ()函数。只要读取的中断状态寄存器不是 0(表示有中断发生) ,就一直循环调用 asm_do_IRQ()。然后,对于 usr模式,调用 ret_to_usr返回用户空间;对于 svc 模式,如果当前进程需要调度,内核又允许抢占,则调用 svc_preempt 进行任务切换,然后从堆栈弹出寄存器,返回到内核模式。至此,汇编语言阶段结束。
 
(2) C 语言部分 
//前面提到,汇编代码是通过调用 asm_do_IRQ()函数进行中断处理的。
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    handle_IRQ(irq, regs);
}

void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    
    irq_enter();//local_irq_count(cpu)++;计数器的值加1,表示正处于具体的中断服务程序中
    
    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);//处理中断
    }
    
    irq_exit();//计数器的值减1
    set_irq_regs(old_regs);
}

int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq);//找到中断号对应的中断描述符结构
    
    if (!desc)
        return -EINVAL;
    
    //根据中断描述符中的信息,得到该中断ISR,并处理中断。    
    generic_handle_irq_desc(irq, desc);
    return 0;
}

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
    //中断初始化时所有中断被设置为handle_edge_irq(),注意:GPIO中断设置时不是这个函数
    desc->handle_irq(irq, desc);//调用handle_edge_irq()
}

void handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
    /*这里锁住描述符的自旋锁,有两个原因:
    1.其他核可能同时收到了中断,将所有同一中断号的中断交给同一个CPU处理,可以避免ISR中做复杂的同步。这个原因是由于unix系统历史原因造成的。
    2.其他核可能在调用request_irq等函数注册ISR,需要使用该锁保护desc中的数据不被破坏。
    注意:这里使用的是raw_spin_lock而不是spin_lock,因为实时内核中,spin_lock已经可以睡眠了。而目前处于硬中断中,不能睡眠。
  */
    raw_spin_lock(&desc->lock);
    
    //清空中断描述符的IRQS_REPLAY和IRQS_WAITING状态,表示这个中断对应的desc正在处理中断
    /*
     IRQS_REPLAY标志是为了挽救丢失的中断。运行到此,中断已经在处理了,就不必考虑挽救丢失的中断了。
   IRQS_WAITING标志表示初始化进程正在等待中断的到来,当探测一些老式设备时,驱动用此方法确定硬件产生的中断号。
   当然,运行到这里,可以将IRQS_WAITING标志去除了,以通知初始化函数,相应的中断号已经触发。
    */
    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

  //如果irqd_irq_disabled表示相应的中断已经被屏蔽,或者irqd_irq_inprogress表示其他核正在处理该中断,或者action为空则说明本中断没有挂接处理函数,则退出
    if (unlikely(irqd_irq_disabled(&desc->irq_data) ||irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
        if (!irq_check_poll(desc)) {
            desc->istate |= IRQS_PENDING;//将istate置位IRQS_PENDING状态,设置中断嵌套标记
            mask_ack_irq(desc);//屏障该中断源
            goto out_unlock;
        }
    }
    //记录下中断在本CPU上触发的次数、本CPU总中断次数。在/proc中要用到这些统计值。
    kstat_incr_irqs_this_cpu(irq, desc);//kstat_this_cpu.irqs[irq]++;
    
    /* Start handling the irq */
    desc->irq_data.chip->irq_ack(&desc->irq_data);//向设备发回响应信号
    
    do {
        if (unlikely(!desc->action)) {//如action为空,则退出
            mask_irq(desc);
            goto out_unlock;
        }
    
        if (unlikely(desc->istate & IRQS_PENDING)) {
            if (!irqd_irq_disabled(&desc->irq_data) &&irqd_irq_masked(&desc->irq_data))
             unmask_irq(desc);
        }
        
        //handle_irq_event要么是在中断上下文调用ISR,要么是唤醒处理线程处理中断。
    //注意:这个函数会临时打开中断描述符的自旋锁。
        handle_irq_event(desc);
    
    } while ((desc->istate & IRQS_PENDING) &&!irqd_irq_disabled(&desc->irq_data));
    
out_unlock:
    raw_spin_unlock(&desc->lock);
}

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    struct irqaction *action = desc->action;
    irqreturn_t ret;
    
  //清除挂起标志。当本核正在调用ISR的过程中,如果发生了同样的中断,那么其他核在收到中断时,会发现本核将IRQD_IRQ_INPROGRESS设置到描述符中。
  //那么其他核会设置IRQS_PENDING并退出。本核在处理完ISR后,会判断此标志并重新执行ISR,在重新执行ISR前,应当将IRQS_PENDING标志清除。
    desc->istate &= ~IRQS_PENDING;
    //在释放自旋锁前,设置IRQD_IRQ_INPROGRESS标志,表示本核正在处理该中断。其他核不应当再处理同样的中断。
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    //在开始调用ISR前,释放自旋锁,避免其他核被长时间挂起。
    raw_spin_unlock(&desc->lock);
    
    //handle_irq_event_percpu会遍历中断描述符中的ISR链表,并回调ISR。
    ret = handle_irq_event_percpu(desc, action);
    
    //重新获得自旋锁,以维持中断描述符中的标志。
    raw_spin_lock(&desc->lock);
    //清除IRQD_IRQ_INPROGRESS标志。此时其他核如果产生同样的中断,则将中断交给其他核处理。
  //如果在处理中断期间,其他核产生了同样的中断,并设置了IRQS_PENDING,则由上层函数继续循环处理中断。
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    return ret;
}

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    //retval表示中断处理结果,默认设置为IRQ_NONE表示该中断没有被ISR响应。
    irqreturn_t retval = IRQ_NONE;
    unsigned int flags = 0, irq = desc->irq_data.irq;
    
    do {//这里的循环是遍历ISR链表,循环调用ISR处理函数。
        irqreturn_t res;    
        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, action->dev_id);//这里调用驱动注册的ISR对中断进行处理。
        trace_irq_handler_exit(irq, action, res);
        
        /*
     * 老版本的内核会根据ISR注册时的标志,在中断处理函数中将中断打开或者关闭。
     * 新版本内核应当是完全实现了中断线程化,长时间运行的中断ISR放到线程中去了,也就是说,在中断上下文都应当是关中断运行。
     * 这里的警告应当是找出那些不符合新版本要求的ISR,在这里打印警告,并强制将中断关闭。
     */
        if (WARN_ONCE(!irqs_disabled(),\"irq %u handler %pF enabled interrupts\\n\",irq, action->handler))
            local_irq_disable();
        
        switch (res) {//根据ISR的返回值决定下一步操作。主要是处理中断线程化。
            case IRQ_WAKE_THREAD://这个中断是线程化了的
                if (unlikely(!action->thread_fn)) {//但是没有注册线程处理函数,警告并退出。正常情况下应当不会走到这个流程。 
                    warn_no_thread(irq, action);
                    break;
                }    
                irq_wake_thread(desc, action);//唤醒本ISR对应的中断线程,由线程执行真正的处理函数。
            case IRQ_HANDLED:// 已经在中断上下文中处理了ISR
                flags |= action->flags;//将所有ISR标志取或,只要其中一个ISR有IRQF_SAMPLE_RANDOM标志,就将本中断作为一个中断源
                break;    
            default:
                break;
        }
        //将所有ISR的返回值取或,那么,只要有一个返回了IRQ_HANDLED,上层都会认为中断得到了正确的处理。
        retval |= res;
        action = action->next;//处理下一个ISR.
    } while (action);
    
    add_interrupt_randomness(irq, flags); /* 随机数机制处理 */ 
    
    /*
   * 在旧版本的内核中,如果多次产生中断而且没有ISR对中断进行响应,那么就会认为是有问题主板并禁用该中断。
   * 新版本内核允许通过调试标志来关闭这个功能。
   * 如果打开了这个功能,则进行中断统计,并在合适的时候关闭该中断。
   */
    if (!noirqdebug)
        note_interrupt(irq, desc, retval);
    return retval;
}

四、内核编程接口 
//真正的中断服务要到具体设备的初始化程序将其中断服务程序通过request_irq()向系统登记,挂入某个中断请求队列以后才会发生
int request_irq(unsigned int irq,void (*handler)(int, void *, struct pt_regs *),unsigned long irqflags,const char * devname,void *dev_id)
{ //irq为中断请求队列的序号,但是这种中断请求号与cpu所用的中断号或中断向量是不同的
    int retval;
    struct irqaction * action;
    
    if (irqflags & SA_SHIRQ) {//表示与其它中断源共用该中断请求通道
        if (!dev_id)//非0
            printk(\"Bad boy: %s (at 0x%x) called us without a dev_id!\\n\", devname, (&irq)[-1]);
    }
    
    if (irq >= NR_IRQS)
        return -EINVAL;
    if (!handler)
        return -EINVAL;
    //通过slab分配器分配struct irqaction结构
    action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);
    if (!action)
        return -ENOMEM;
    
    action->handler = handler;//指向具体的中断服务程序
    action->flags = irqflags;
    action->mask = 0;
    action->name = devname;//设备名
    action->next = NULL;
    action->dev_id = dev_id;//设备表示符
    
    retval = setup_irq(irq, action);//将这个irqaction结构链入相应的中断请求队列
    if (retval)
        kfree(action);
    return retval;
}

int setup_irq(unsigned int irq, struct irqaction * new)
{
    int shared = 0;
    unsigned long flags;
    struct irqaction *old, **p;
    irq_desc_t *desc = irq_desc + irq;//根据中断号找到中断请求队列的数组
    
    if (new->flags & SA_SAMPLE_RANDOM) {
        rand_initialize_irq(irq);//为产生随机数使用
    }
    
    spin_lock_irqsave(&desc->lock,flags);//上锁进入临界区
    p = &desc->action;
    if ((old = *p) != NULL) {//如果这个中断对应的中断描述符desc中已经设置了action字段,若中断共享标志没设置,则返回错误
        if (!(old->flags & new->flags & SA_SHIRQ)) {//是否允许共用一个中断通道
            spin_unlock_irqrestore(&desc->lock,flags);
            return -EBUSY;
        }
    
        //若此中断允许共享,则找到请求描述符字段irqaction队列的末尾,将新的irqaction添加到队列中
        do {
            p = &old->next;
            old = *p;
        } while (old);//一直循环找到irq_desc[irq]这条通道中中断请求队列的尾部
        shared = 1;//表示允许中断共享
    }
    *p = new;//将新的interrupt挂入队列尾部
    
    if (!shared) {
        desc->depth = 0;
        desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING);
        desc->handler->startup(irq);
    }
    spin_unlock_irqrestore(&desc->lock,flags);//退出临界区
    
    register_irq_proc(irq);//创建proc文件系统的相关文件
    return 0;    
}

抱歉!评论已关闭.