linux中断分析

cpu的模式:
模式切换:硬件自动完成
arm在irq/svc/abort几种模式下sp是不能共用的

user
用户模式
fiq

irq

svc

abt

und

sys

mon

31 30 29 28 27 26 25 24 23 20 19 16 15 10 9 8 7 6 5 4 0
N | Z| C |V |Q| IT J reser GE IT E A I F T M

M[4:0]
10000 user
10001 fiq
10010 irq
10011 svc
10111 abt
11011 und
11111 sys
10110 mon

此时lr中保存的实际上是异常的返回地址,异常发生,切换到svc模式后,会将lr保存到svc模式栈中

  • (pt_reg->pc),最后从异常返回时再将pt_reg->pc加载如arm寄存器pc中,实现异常返回。本函数只是
  • 其中一个步骤,即为将异常发生时刻lr保存到svc模式栈中(pt_reg->pc)做准备。
    *5. spsr是异常发生那一刻(即进入异常模式前是什么模式)的cpsr状态,如内核态下发生中断,则spsr是
  • svc模式下10011,如用户态下发生中断,则spsr是user模式10000。
    *6. 此时cpu正处于异常状态(如中断),此时cpsr为10010。
    *7. 要进行真正的异常处理,需要退出异常模式进入svc模式。

工作:
1)cpu状态的切换
cpu -> irq -> svc

2)在不同模式保存数据到栈中
sp — irq 报错lr 和 cpsr 信息
sp — svc

3)调用 handle_arch_irq 函数

    .macro vector_stub, name, mode, correction=0
    .align 5 //强制对齐32字节对齐

vector_\name:
.if \correction
/*
*需要调整返回值,对应irq异常将lr减去4,因为异常发生时,arm将pc地址+4赋值给了lr。
*/
sub lr, lr, #\correction
.endif

	@save r0,lr;将r0和lr保存到异常模式的栈上[sp] = r0;[sp+4] = lr_irq;
	
    stmia sp, {r0, lr} @ save r0, lr
    mrs lr, spsr //得到异常发生时所处模式得信息

	/*
	 *将spsr保存到异常模式的栈上[sp+8]=spsr_irq=lr
	*/
	str lr, [sp, #8] @ save spsr

	/*
	 *1. dabt处理时,r0=r0^(0x17^0x13)=r0^0x4,bit3取反之后10011变为svc模式;
	 *2. irq处理时:r0=10010=r0^(0x12^0x13)=r0^0x1=10011变为svc模式
	*/
	mrs r0, cpsr
	eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
	msr spsr_cxsf, r0


	/*
	*r0=sp;
	*注意:
	*1. 此时r0中保存了异常状态下sp栈地址,这个栈上保存了r0,lr(异常返回地址),spsr(异常发生时,cpu
	* 的状态,当然异常返回时需要恢复该状态)
	*2. 之后的函数会把r0中保存的异常模式的sp上信息,加载到svc模式下的sp栈上。异常处理返回时再将svc
	* mode 的栈加载到arm寄存器上。
	*/
	mov r0, sp
	//应该再切换到svc 之前把sp保存到r0中 ***************

	/*
	*lr中保存发生异常时arm的cpsr状态到spsr
	*1. usr模式发生异常则lr=10000&0x0f;lr=pc+lr<<2 pc+0时执行 __irq_usr;
	*2. svc模式发生异常则lr=10011&0x0f;lr=pc+lr<<2 pc+12时执行 __irq_svc
	*/
    ARM( ldr lr, [pc, lr, lsl #2] )

	/* movs中s表示把spsr恢复给cpsr,上面可知spsr保存的是svc模式,不过此时中断还是关闭的
	 * 异常处理一定要进入svc模式原因:
	 *(1)异常处理一定要PL1特权级。
	 *(2)使能嵌套中断。
	 * 如果一个中断模式(例如用户态发生中断,arm从usr进入irq模式)中重新允许中断,
	 * 且这个中断模式中使用了bl指令,bl会把pc放到lr_irq中,这个地址会被当前模式下产生的中断破坏
	 * 这种情况下中断无法返回。所以为了避免这种情况,中断处理过程应该切换到svc模式,bl指令可以把
	 * pc(即子程序返回地址)保存到lr_svc.
	 */
			movs pc, lr @ branch to handler in SVC mode
	ENDPROC(vector_\name)

    .long __irq_usr     @ 0 (USR_26 / USR_32) 从用户态下进入的irq,执行__irq_usr代码
    .long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
    .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
    .long __irq_svc     @ 3 (SVC_26 / SVC_32) 从内核态下进入的irq,执行__irq_svc代码
    .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

//此时arm处于svc模式执行下面代码
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler //irq处理函数
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq //中断处理完成返回
UNWIND(.fnend )
ENDPROC(__irq_usr)/*

    .macro usr_entry, trace=1, uaccess=1

UNWIND(.fnstart )
UNWIND(.cantunwind ) @ don’t unwind the user space

	/*
	 *arch/arm/kernel/asm-offsets.c中定义DEFINE(S_FRAME_SIZE,sizeof(struct pt_regs)) 
	 *S_FRAME_SIZE=72
	*/
	sub sp, sp, #S_FRAME_SIZE

/*
*注释:
*宏CONFIG_MULTI_IRQ_HANDLER在.config中有定义,会将handle_arch_irq里的值赋值给pc去执行。
*给handle_arch_irq请看后面的C语言阶段分析
/
/

  • Interrupt handling.
    */
    .macro irq_handler
    #ifdef CONFIG_MULTI_IRQ_HANDLER
    ldr r1, =handle_arch_irq
    mov r0, sp
    badr lr, 9997f
    ldr pc, [r1] //进入C语言阶段的中断处理
    #else
    arch_irq_handler_default
    #endif
    9997:
    .endm

======================================中断向量表和中断处理部分代码的搬移

early_trap_init函数的调用流程为:
start_kernel(init/main.c)—>
setup_arch(arch/arm/kernel/setup.c)—>
paging_init(arch/arm/mm/mmu.c)—>
devicemaps_init(arch/arm/mm/mmu.c)—>
early_trap_init(arch/arm/kernel/traps.c)

static void __init devicemaps_init(const struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
void vectors;
/

* Allocate the vector page early.
*分配两个页的内存空间,arm中每个页的大小为4K,这两个页的内存空间,一个是为保存中断向量
*表,一个是为了保存中断的处理部分代码,这两部分代码的排布可以在
*(arch/arm/kernel/vmlinux.lds和arch/arm/kernel/entry-armv.S)中可以具体分析出来
*/
vectors = early_alloc(PAGE_SIZE * 2);
early_trap_init(vectors);

    /*
     *创建一个页的内存地址映射,虚拟地址为0xffff0000,此地址为中断向量表的高端地址
     *设置中断向量表的高端地址在汇编的v7_setup中,使用的v7_crval设置了cp15的c1寄存器
     *v7_crval定义在arch/arm/mm/proc-v7-2level.S。
     */
    map.pfn = __phys_to_pfn(virt_to_phys(vectors));
    map.virtual = 0xffff0000;
    map.length = PAGE_SIZE;

#ifdef CONFIG_KUSER_HELPERS //此宏有定义
map.type = MT_HIGH_VECTORS;
#else
map.type = MT_LOW_VECTORS;
#endif
create_mapping(&map);
/*
*判断中断向量表的位置是否设置在高端地址,如果中断向量表没有设置在高端地址,
*在映射低端中断向量表地址。
*/
if (!vectors_high()) {
map.virtual = 0;
map.length = PAGE_SIZE * 2;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}

    /* Now create a kernel read-only mapping */
    map.pfn += 1;
    map.virtual = 0xffff0000 + PAGE_SIZE;
    map.length = PAGE_SIZE;
    map.type = MT_LOW_VECTORS;
    create_mapping(&map);








    .align  2
    .type   v7_crval, #object

v7_crval:
crval clear=0x2120c302, mmuset=0x10c03c7d, ucset=0x00c01c7c

	/*
     * 将申请的4K先设置为未定义指令,防止在发生其他中断时,没有处理导致cpu错误
    */
    for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
            ((u32 *)vectors_base)[i] = 0xe7fddef1;/*


    /*
     *将中断向量表和中断处理的代码搬移到申请的两页地址空间内
     */
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);

    kuser_init(vectors_base);

    flush_icache_range(vectors, vectors + PAGE_SIZE * 2);

void __init early_trap_init(void *vectors_base)
{

    unsigned long vectors = (unsigned long)vectors_base;
    extern char __stubs_start[], __stubs_end[];
    extern char __vectors_start[], __vectors_end[];
    unsigned i;

	/*
     * 将申请的4K先设置为未定义指令,防止在发生其他中断时,没有处理导致cpu错误
    */
    for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
            ((u32 *)vectors_base)[i] = 0xe7fddef1;


    /*
     *将中断向量表和中断处理的代码搬移到申请的两页地址空间内
    */
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);

    kuser_init(vectors_base);

    flush_icache_range(vectors, vectors + PAGE_SIZE * 2);

//init/main.c
asmlinkage void __init start_kernel(void)

{
……
trap_init(); //空函数
……

early_irq_init();

init_IRQ();

……

}

//arch/arm/kernel/irq/irqdesc.c/
int __init early_irq_init(void)
{
int i, initcnt, node = first_online_node;
struct irq_desc *desc;

    init_irq_default_affinity();  //由CONFIG_SMP宏决定是否为空函数,见下面分析一

    /* Let arch update nr_irqs and return the nr of preallocated irqs */
    initcnt = arch_probe_nr_irqs();//体系结构相关的代码来决定预先分配的中断描述符的个数见分析二
   
    if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))  //见下面分析三
            nr_irqs = IRQ_BITMAP_BITS;

    if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
            initcnt = IRQ_BITMAP_BITS;

    if (initcnt > nr_irqs)
            nr_irqs = initcnt;
			
	//事先构造好 irq_desc 结构体
    for (i = 0; i < initcnt; i++) {
            desc = alloc_desc(i, node, NULL);  //分配 struct irq_desc结构,分析四
            
			//用于标记那些irq 申请占用  allocated_irqs 关键变量
			set_bit(i, allocated_irqs);  	//设置相应的位数组,分析三
            irq_insert_desc(i, desc);  		//保存irq_desc指针,分析四
    }
    return arch_early_irq_init(); //kernel/softirq.c中定义,为空函数

}

//arch/arm/kernel/irq.c
#ifdef CONFIG_SPARSE_IRQ
int __init arch_probe_nr_irqs(void)
{
nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS;//NR_IRQS 16
return nr_irqs; //分析下面的分析,得知返回的是NR_IRQS
}
#endif

//kernel/irq/internals.h

//include/linux/types.h
#define DECLARE_BITMAP(name,bits)
unsigned long name[BITS_TO_LONGS(bits)]

//arch/arm/include/asm/bitops.h
#define set_bit(nr,p) ATOMIC_BITOP(set_bit,nr,p) //_set_bit
#define clear_bit(nr,p) ATOMIC_BITOP(clear_bit,nr,p)
#define change_bit(nr,p) ATOMIC_BITOP(change_bit,nr,p)
#define test_and_set_bit(nr,p) ATOMIC_BITOP(test_and_set_bit,nr,p)
#define test_and_clear_bit(nr,p) ATOMIC_BITOP(test_and_clear_bit,nr,p)
#define test_and_change_bit(nr,p) ATOMIC_BITOP(test_and_change_bit,nr,p)

/*

  • __builtin_constant_p 是编译器gcc内置函数,用于判断一个值是否为编译时常量,
  • 如果是常数,函数返回1 ,否则返回0。此内置函数的典型用法是在宏中用于手动编译时优化
    /
    #ifndef CONFIG_SMP //此宏在.config中未定义
    /
  • The __* form of bitops are non-atomic and may be reordered.
    */
    #define ATOMIC_BITOP(name,nr,p)
    (__builtin_constant_p(nr) ? ___atomic##name(nr, p) : _##name(nr,p))
    #else
    #define ATOMIC_BITOP(name,nr,p) _##name(nr,p)
    #endif

//arch/arm/lib/setbit.S

#include <linux/linkage.h>
#include <asm/assembler.h>
#include “bitops.h”
.text
//bitop 为宏
bitop _set_bit, orr

//arch/arm/lib/bitops.h
.macro bitop, name, instr
ENTRY( \name )
UNWIND( .fnstart )
ands ip, r1, #3
strneb r1, [ip] @ assert word-aligned
mov r2, #1
and r3, r0, #31 @ Get bit offset
mov r0, r0, lsr #5
add r1, r1, r0, lsl #2 @ Get word offset
#if LINUX_ARM_ARCH >= 7 && defined(CONFIG_SMP)
.arch_extension mp
ALT_SMP(W(pldw) [r1])
ALT_UP(W(nop))
#endif
mov r3, r2, lsl r3
1: ldrex r2, [r1]
\instr r2, r2, r3
strex r0, r2, [r1]
cmp r0, #0
bne 1b
bx lr
UNWIND( .fnend )
ENDPROC(\name )
.endm

//driver/irqchip/irq-vic.c
/*
*通过IRQCHIP_DECLARE定义一个位于__irqchip_of_table段的全局量struct of_device_id
*__of_table_arm_pl192_vic,linux在中断初始化阶段在设备树中寻找是否有对应的节点,找到
*相对应的节点后,随即执行vic_of_init
*/
IRQCHIP_DECLARE(arm_pl192_vic, “arm,pl192-vic”, vic_of_init);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值