Linux 中断异常分析

语言描述:
深入理解LINUX内核中,关于X86系列的描述:

异常是同步中断,中断时异步中断;都用中断向量表表示;
中断向量表
    中断描述符0
    中断描述符1
    ......
    中断描述符255
其中,中断描述符为中断门描述符、任务门描述符、陷阱们描述符之一;每个描述符都包含特权等级,段地址,处理程序偏移地址;

接着分析我们的ARM:
Q:ARM为什么能支持如此多的中断请求函数,就是用request_irq()注册的中断函数;
A:device A 产生中断——》中断控制器——》传递给指定的CPU——》CPU产生中断,进入中断处理子程序(汇编)——》中断处理子程序(C语言)——》根据device A的IRQ Line编号,选择request_irq()注册中断函数开始执行;


Q:ARM核收到中断信号后,如何处理?
A:(自动)暂定当前CPU,将当前CPSR寄存器保存到中断模式的SPSR中,将CPSR的模式位改为中断模式,将部分当前寄存器切换为中断模式的专用寄存器;比如SP,LR寄存器;将PC寄存器值保存到中断模式的LR寄存器中;将中断处理子程序(汇编)地址加载到PC中,恢复当前CPU运行;
中断处理器子程序:
1 设置SP指向当前CPU中断栈;
2 保存非专有寄存器值到当前中断栈中;
3 执行中断处理……
4 恢复非专有寄存器到对应寄存器中;
5 不确定SP是否需要处理,因为SP当前是中断模式专有的;可能不需要处理;需要看代码;
6 将SPSR恢复到CPSR中;
7 将当前LR的值传给PC寄存器,应该要处理一下,因为PC寄存器指向的下一条指令是超前的;
继续执行,那么就很自然的恢复前一个模式继续处理;
如果在这其中还有新的中断、异常产生,那么follow up, go ahead;


上面的理论知识理论,需要源码来验证:

arch/arm/kernel/entry-armv.S
__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)  pc, __vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq
    W(b)    vector_fiq


----------


/*
 * 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


----------


/*
 * Vector stubs.
 *
 * This code is copied to 0xffff1000 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not exceed
 * a page size.
 *
 * 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.
 */
    .macro  vector_stub, name, mode, correction=0
    .align  5

vector_\name:
    .if \correction
    sub lr, lr, #\correction
    .endif

    @
    @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
    @ (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)

以上三部分是kernel关于ARM中断异常的汇编处理流程;其中vector_irq是vector_stub irq, IRQ_MODE, 4生成的,我们分析vector_stub这个macro;
首先前三句,表明当中断发生时,pc寄存器的值已经保存到了irq模式的lr寄存器中,这里lr-4意味着,中断发生时pc寄存器超前2条指令,所以回退一条;
第二部分,如注释所说,保存r0, lr寄存器中的值到sp指向的栈中,因为已经进入中断处理,如语言描述部分,当前sp指向的是当前cpu的中断栈;将spsr_irq借助lr保存到sp指向的栈中,
第三部分,切换到SVC32模式;
第四部分,因为当前非thumb2内核,所以THUMB()全部无效,直接无视,查看ARM()及下面的汇编,表明会跳转到Interrupt dispatcher的__irq_svc部分;

__irq_svc:
    svc_entry
    irq_handler

#ifdef CONFIG_PREEMPT
    get_thread_info tsk
    ldr r8, [tsk, #TI_PREEMPT]      @ get preempt count
    ldr r0, [tsk, #TI_FLAGS]        @ get flags
    teq r8, #0              @ if preempt count != 0
    movne   r0, #0              @ force flags to 0
    tst r0, #_TIF_NEED_RESCHED
    blne    svc_preempt
#endif

    svc_exit r5, irq = 1            @ return from exception
 UNWIND(.fnend      )    
ENDPROC(__irq_svc)


----------


/*
 * Interrupt handling.
 */
    .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


----------


#ifdef CONFIG_MULTI_IRQ_HANDLER
    .globl  handle_arch_irq
handle_arch_irq:
    .space  4
#endif

这部分大体上比较好理解,调用irq_handler,由于我的板子配置了MULTI_IRQ_HANDLE,所以会跳转到handle_arch_irq这个全局变量处;
这个全局变量有两个地方可以设置:

handle_arch_irq = mdesc->handle_irq;
or
#ifdef CONFIG_MULTI_IRQ_HANDLER
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
    if (handle_arch_irq)
        return;

    handle_arch_irq = handle_irq;
}
#endif

先说一下,CONFIG_MULTI_IRQ_HANDLER表示,handle_arch_irq可以动态的在运行过程更改,当然,从set_handle_irq()中看出,也只能改一次;
第一种方式,较为传统,我的板子上mdesc->handle_irq为NULL;
第二种方式,我的板子是rk3288,是一块arm cotex-a15的AP,它采用的是ARM提供的公版中断控制器;如下:

drivers/irqchip/irq-gic.c
    IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
    IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
    IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
    IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);

我的板子在devicetree中配置了’arm,cortex-a15-gic’,所以会选择cortex_a15_gic;
分析IRQ_CHIP_DECLARE()宏:

/*  
 * This macro must be used by the different irqchip drivers to declare
 * the association between their DT compatible string and their
 * initialization function.
 *
 * @name: name that must be unique accross all IRQCHIP_DECLARE of the
 * same file.
 * @compstr: compatible string of the irqchip driver
 * @fn: initialization function
 */ 
#define IRQCHIP_DECLARE(name,compstr,fn)                \
    static const struct of_device_id irqchip_of_match_##name    \
    __used __section(__irqchip_of_table)                \
    = { .compatible = compstr, .data = fn }

#endif

先看重点,它位于__irqchip_of_table段中,查看vmlinux的符号表System.map

c0cc0ac0 T __irqchip_begin
c0cc0ac0 t irqchip_of_match_msm_qgic2
c0cc0b84 t irqchip_of_match_msm_8660_qgic
c0cc0c48 t irqchip_of_match_cortex_a9_gic
c0cc0d0c t irqchip_of_match_cortex_a15_gic
c0cc0dd0 t irqchip_of_match_end

位于__irqchip_begin和irqchip_of_match_end之间

./drivers/irqchip/irqchip.c
void __init irqchip_init(void)
{
    of_irq_init(__irqchip_begin);
}
./arch/arm/kernel/irq.c
void __init init_IRQ(void)                                                  
{   
    if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)                   
        irqchip_init();                                                     
    else
        machine_desc->init_irq();                                           
}
./init/main.c
early_irq_init();
init_IRQ();

这个调用流程清楚了,现在整理一下调用流程:

start_kernel()
    early_irq_init()
    init_IRQ()
        irqchip_init()
            of_irq_init(__irqchip_begin)
                gic_of_init()
                    gic_init_bases()
                        set_handle_irq(gic_handle_irq)

这样,最终将handle_arch_irq设置为gic_handle_irq,也就是说,当发生中断时,最终调用到gic_handle_irq;

early_irq_init()
    init_irq_default_affinity()
    初始化中断在SMP中与具体CPU的亲和度;

    arch_probe_nr_irqs()
    返回具体arch的中断数;

    for (i = 0; i < initcnt; i++) {
        desc = alloc_desc(i, node, NULL);
        set_bit(i, allocated_irqs);
        irq_insert_desc(i, desc);
    }
    初始化所有的中断,为每一个irq分配一个struct irq_desc,这个结构用来描述一个具体的中断,然后设置到allocated_irqs的bitmap中占位,接着调用irq_insert_desc将,struct irq_desc插入irq_desc_tree中,也就是一个radix_tree结构;

看了irq_insert_desc后,因为它是irq:irq_desc的键值对,所以,有理由相信,request_irq最终肯定会重新初始化irq对应的irq_desc结构;
根据猜测,看看request_irq()具体干了什么:

request_irq
    request_threaded_irq
        irq_to_desc(irq)
        kzalloc(sizeof(struct irqaction), GFP_KERNEL)
        action->handler = handler;
        action->thread_fn = thread_fn;
        action->flags = irqflags;
        action->name = devname;
        action->dev_id = dev_id;
        __setup_irq(irq, desc, action)

继续分析第二个函数:init_IRQ()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值